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 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, ); }