119 lines
3.8 KiB
Dart
119 lines
3.8 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:carddex_demo/native/phash_loader.dart';
|
|
import 'package:carddex_demo/services/hash_library.dart';
|
|
import 'package:image/image.dart' as img;
|
|
import 'package:path/path.dart' as p;
|
|
|
|
const _imageExts = {'.jpg', '.jpeg', '.png', '.webp', '.bmp'};
|
|
|
|
/// Same pipeline as CardCropper.cropAndCorrect so library hashes match
|
|
/// the same conditions that query hashes are computed under.
|
|
const _cropW = 280;
|
|
const _cropH = 390;
|
|
const _borderTrim = 10;
|
|
|
|
Future<void> main(List<String> args) async {
|
|
final root = Directory.current.path;
|
|
final assetsDir = Directory(p.join(root, 'test_assets'));
|
|
final outHashes = p.join(root, 'data', 'hashes.json');
|
|
final outCatalog = p.join(root, 'data', 'catalog.json');
|
|
final libPath = _resolveLib(root, args);
|
|
|
|
if (!assetsDir.existsSync()) {
|
|
stderr.writeln('test_assets/ not found at ${assetsDir.path}');
|
|
exit(1);
|
|
}
|
|
|
|
final native = PhashNative(hostLibraryPath: libPath);
|
|
final files = assetsDir
|
|
.listSync()
|
|
.whereType<File>()
|
|
.where((f) => _imageExts.contains(p.extension(f.path).toLowerCase()))
|
|
.toList()
|
|
..sort((a, b) => p.basename(a.path).compareTo(p.basename(b.path)));
|
|
|
|
stdout.writeln('hashgen — ${files.length} images from ${assetsDir.path}');
|
|
stdout.writeln('libphash — $libPath');
|
|
|
|
final catalogItems = <Map<String, String>>[];
|
|
final hashItems = <HashEntry>[];
|
|
|
|
for (final file in files) {
|
|
final filename = p.basename(file.path);
|
|
final cardId = p.basenameWithoutExtension(filename);
|
|
try {
|
|
final raw = await file.readAsBytes();
|
|
var decoded = img.decodeImage(raw);
|
|
if (decoded == null) {
|
|
stderr.writeln('skip $cardId: decode failed');
|
|
continue;
|
|
}
|
|
decoded = img.bakeOrientation(decoded);
|
|
// Match the App pipeline: resize → trim border → re-encode JPEG q=98.
|
|
var resized = img.copyResize(decoded,
|
|
width: _cropW, height: _cropH,
|
|
interpolation: img.Interpolation.cubic);
|
|
resized = img.copyCrop(resized,
|
|
x: _borderTrim,
|
|
y: _borderTrim,
|
|
width: _cropW - _borderTrim * 2,
|
|
height: _cropH - _borderTrim * 2);
|
|
final hashedBytes = img.encodeJpg(resized, quality: 98);
|
|
|
|
final hash = native.hashFromBytes(hashedBytes);
|
|
hashItems.add(HashEntry(cardId: cardId, hash: hash, filename: filename));
|
|
catalogItems.add({
|
|
'card_id': cardId,
|
|
'name': cardId,
|
|
'image': 'test_assets/$filename',
|
|
'filename': filename,
|
|
});
|
|
stdout.writeln(' $cardId');
|
|
} catch (e) {
|
|
stderr.writeln('skip $cardId: $e');
|
|
}
|
|
}
|
|
|
|
final library = HashLibrary(
|
|
version: 'assets-v1',
|
|
algorithm: 'rgb-phash-v1',
|
|
hashBits: 105,
|
|
items: hashItems,
|
|
);
|
|
await library.saveFile(outHashes);
|
|
|
|
final catalog = {
|
|
'version': 'assets-v1',
|
|
'algorithm': 'rgb-phash-v1',
|
|
'items': catalogItems,
|
|
};
|
|
final catalogFile = File(outCatalog);
|
|
await catalogFile.parent.create(recursive: true);
|
|
await catalogFile.writeAsString(
|
|
'${const JsonEncoder.withIndent(' ').convert(catalog)}\n',
|
|
);
|
|
|
|
stdout.writeln('saved ${hashItems.length} hashes → $outHashes');
|
|
stdout.writeln('catalog → $outCatalog');
|
|
}
|
|
|
|
String _resolveLib(String root, List<String> args) {
|
|
for (var i = 0; i < args.length; i++) {
|
|
if (args[i] == '--lib' && i + 1 < args.length) {
|
|
return p.normalize(p.absolute(args[i + 1]));
|
|
}
|
|
}
|
|
final dylib = p.join(root, 'native', 'phash', 'build', 'libphash.dylib');
|
|
final so = p.join(root, 'native', 'phash', 'build', 'libphash.so');
|
|
if (File(dylib).existsSync()) return dylib;
|
|
if (File(so).existsSync()) return so;
|
|
stderr.writeln(
|
|
'libphash not found. Build first:\n'
|
|
' cmake -S native/phash -B native/phash/build -DPHASH_BUILD_SHARED=ON\n'
|
|
' cmake --build native/phash/build',
|
|
);
|
|
exit(1);
|
|
}
|