card-recog-demo/lib/native/phash_loader.dart
2026-07-14 22:51:07 -07:00

109 lines
2.9 KiB
Dart

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 = <String>[
'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 = <String>[
'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<PhashFromBytesNative, PhashFromBytesDart>(
'phash_from_bytes',
),
lib.lookupFunction<PhashHammingNative, PhashHammingDart>(
'phash_hamming',
),
);
}
final PhashFromBytesDart _fromBytes;
final PhashHammingDart _hamming;
/// Compute 105-bit ASCII hash from encoded image bytes (JPEG/PNG/…).
String hashFromBytes(List<int> bytes) {
if (bytes.isEmpty) {
throw ArgumentError('image bytes are empty');
}
final data = malloc<Uint8>(bytes.length);
final out = malloc<Uint8>(kPhashBits + 1);
try {
data.asTypedList(bytes.length).setAll(0, bytes);
final rc = _fromBytes(data, bytes.length, out.cast<Char>(), kPhashBits + 1);
if (rc != 0) {
throw StateError('phash_from_bytes failed with code $rc');
}
return out.cast<Utf8>().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<Char>(), pb.cast<Char>());
if (d < 0) {
throw StateError('phash_hamming failed');
}
return d;
} finally {
malloc.free(pa);
malloc.free(pb);
}
}
}