import 'dart:ffi'; import 'dart:io'; import 'package:ffi/ffi.dart'; import 'phash_bindings.dart'; /// Loads the vendored C++ `libphash` for the current platform. DynamicLibrary loadPhashLibrary({String? hostLibraryPath}) { if (hostLibraryPath != null && hostLibraryPath.isNotEmpty) { return DynamicLibrary.open(hostLibraryPath); } if (Platform.isAndroid) { return DynamicLibrary.open('libphash.so'); } if (Platform.isIOS) { try { return DynamicLibrary.process(); } catch (_) { return DynamicLibrary.open('phash.framework/phash'); } } if (Platform.isMacOS) { final candidates = [ 'native/phash/build/libphash.dylib', 'native/phash/build/libphash.so', 'libphash.dylib', 'libphash.so', ]; for (final path in candidates) { final file = File(path); if (file.existsSync()) { return DynamicLibrary.open(file.absolute.path); } } return DynamicLibrary.open('libphash.dylib'); } if (Platform.isLinux) { final candidates = [ 'native/phash/build/libphash.so', 'libphash.so', ]; for (final path in candidates) { final file = File(path); if (file.existsSync()) { return DynamicLibrary.open(file.absolute.path); } } return DynamicLibrary.open('libphash.so'); } throw UnsupportedError('phash FFI is not supported on ${Platform.operatingSystem}'); } /// Thin FFI wrapper around rgb-phash-v1 C API. class PhashNative { PhashNative._(this._fromBytes, this._hamming); factory PhashNative({String? hostLibraryPath}) { final lib = loadPhashLibrary(hostLibraryPath: hostLibraryPath); return PhashNative._( lib.lookupFunction( 'phash_from_bytes', ), lib.lookupFunction( 'phash_hamming', ), ); } final PhashFromBytesDart _fromBytes; final PhashHammingDart _hamming; /// Compute 105-bit ASCII hash from encoded image bytes (JPEG/PNG/…). String hashFromBytes(List bytes) { if (bytes.isEmpty) { throw ArgumentError('image bytes are empty'); } final data = malloc(bytes.length); final out = malloc(kPhashBits + 1); try { data.asTypedList(bytes.length).setAll(0, bytes); final rc = _fromBytes(data, bytes.length, out.cast(), kPhashBits + 1); if (rc != 0) { throw StateError('phash_from_bytes failed with code $rc'); } return out.cast().toDartString(); } finally { malloc.free(data); malloc.free(out); } } int hamming(String a, String b) { final pa = a.toNativeUtf8(); final pb = b.toNativeUtf8(); try { final d = _hamming(pa.cast(), pb.cast()); if (d < 0) { throw StateError('phash_hamming failed'); } return d; } finally { malloc.free(pa); malloc.free(pb); } } }