You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
3.0 KiB
Dart

import 'dart:convert';
import 'dart:math';
import 'package:image/image.dart' as img;
class FingerprintValidation {
final bool isComplete;
final double coverageRatio; // % of "ridge" pixels
final double contrast; // std dev of pixel intensity
final String reason;
FingerprintValidation({
required this.isComplete,
required this.coverageRatio,
required this.contrast,
required this.reason,
});
}
FingerprintValidation validateFingerprintBase64(
String base64Image, {
double minCoverage = 0.20, // tune per scanner DPI/size
double maxCoverage = 0.65,
double minContrast = 25.0,
double edgeCutoffThreshold = 0.45, // % of border pixels that are "ridge"
}) {
final bytes = base64Decode(base64Image);
final image = img.decodeImage(bytes);
if (image == null) {
return FingerprintValidation(
isComplete: false,
coverageRatio: 0,
contrast: 0,
reason: 'decode_failed',
);
}
final gray = img.grayscale(image);
final w = gray.width, h = gray.height;
int ridgePixels = 0;
int total = 0;
double sum = 0, sumSq = 0;
// simple Otsu-ish threshold: use mean as baseline, refine below
final List<int> intensities = [];
for (final px in gray) {
final v = img.getLuminance(px).toDouble();
intensities.add(v.toInt());
sum += v;
total++;
}
final mean = sum / total;
for (final v in intensities) {
sumSq += (v - mean) * (v - mean);
}
final contrast = sqrt(sumSq / total);
// threshold: pixels darker than mean - 0.5*std are "ridges"
final threshold = mean - (contrast * 0.5);
for (final v in intensities) {
if (v < threshold) ridgePixels++;
}
final coverage = ridgePixels / total;
// edge/border check — if ridge pattern is heavily cut off at edges,
// finger was likely partial or misplaced
int borderRidge = 0, borderTotal = 0;
for (int x = 0; x < w; x++) {
for (final y in [0, h - 1]) {
final v = img.getLuminance(gray.getPixel(x, y)).toDouble();
borderTotal++;
if (v < threshold) borderRidge++;
}
}
for (int y = 0; y < h; y++) {
for (final x in [0, w - 1]) {
final v = img.getLuminance(gray.getPixel(x, y)).toDouble();
borderTotal++;
if (v < threshold) borderRidge++;
}
}
final borderRatio = borderTotal == 0 ? 0 : borderRidge / borderTotal;
String reason = 'ok';
bool isComplete = true;
if (contrast < minContrast) {
isComplete = false;
reason = 'Press finger on the scanner with more pressure';
} else if (coverage < minCoverage) {
isComplete = false;
reason = 'Place finger fully on the scanner';
} else if (coverage > maxCoverage) {
isComplete = false;
reason = 'Place finger lightly on the scanner';
} else if (borderRatio > edgeCutoffThreshold) {
isComplete = false;
reason = 'Finger is not centered on the scanner';
}
return FingerprintValidation(
isComplete: isComplete,
coverageRatio: coverage,
contrast: contrast,
reason: reason,
);
}