feat: convert NEDO XML templates to Hanvon face photos
Detect NEDO blobs, extract/prepare JPEG for Hanvon upload.main
parent
7741523d25
commit
b95fc02f74
|
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.Configuration;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
|
||||
namespace HanvonF710XAttendanceService
|
||||
{
|
||||
internal static class NedoPhotoPreprocessor
|
||||
{
|
||||
public const int DefaultMinShortSide = 480;
|
||||
public const int HanvonMaxSide = 1024;
|
||||
public const int HanvonMaxBytes = 500 * 1024;
|
||||
|
||||
public static bool TryPrepareForHanvon(string photoBase64, out string preparedBase64, out int width, out int height, out string note)
|
||||
{
|
||||
preparedBase64 = null;
|
||||
width = 0;
|
||||
height = 0;
|
||||
note = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(photoBase64))
|
||||
{
|
||||
note = "empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] sourceBytes;
|
||||
try
|
||||
{
|
||||
sourceBytes = Convert.FromBase64String(photoBase64);
|
||||
}
|
||||
catch
|
||||
{
|
||||
note = "invalid_base64";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsCompleteJpeg(sourceBytes))
|
||||
{
|
||||
note = "incomplete_jpeg";
|
||||
return false;
|
||||
}
|
||||
|
||||
int minShortSide = GetIntSetting("NEDO_FACE_MIN_SHORT_SIDE", DefaultMinShortSide);
|
||||
byte[] outputBytes;
|
||||
using (var inputStream = new MemoryStream(sourceBytes))
|
||||
using (var image = Image.FromStream(inputStream))
|
||||
{
|
||||
width = image.Width;
|
||||
height = image.Height;
|
||||
int shortSide = Math.Min(width, height);
|
||||
if (shortSide >= minShortSide && sourceBytes.Length <= HanvonMaxBytes)
|
||||
{
|
||||
preparedBase64 = photoBase64;
|
||||
note = "unchanged";
|
||||
return true;
|
||||
}
|
||||
|
||||
double scale = 1.0;
|
||||
if (shortSide < minShortSide)
|
||||
{
|
||||
scale = (double)minShortSide / shortSide;
|
||||
}
|
||||
|
||||
int targetWidth = (int)Math.Round(width * scale);
|
||||
int targetHeight = (int)Math.Round(height * scale);
|
||||
int longSide = Math.Max(targetWidth, targetHeight);
|
||||
if (longSide > HanvonMaxSide)
|
||||
{
|
||||
double fit = (double)HanvonMaxSide / longSide;
|
||||
targetWidth = (int)Math.Round(targetWidth * fit);
|
||||
targetHeight = (int)Math.Round(targetHeight * fit);
|
||||
}
|
||||
|
||||
using (var bitmap = new Bitmap(targetWidth, targetHeight))
|
||||
using (var graphics = Graphics.FromImage(bitmap))
|
||||
{
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
graphics.DrawImage(image, 0, 0, targetWidth, targetHeight);
|
||||
|
||||
using (var outputStream = new MemoryStream())
|
||||
{
|
||||
var encoder = GetJpegEncoder();
|
||||
if (encoder != null)
|
||||
{
|
||||
using (var parameters = new EncoderParameters(1))
|
||||
{
|
||||
parameters.Param[0] = new EncoderParameter(Encoder.Quality, 92L);
|
||||
bitmap.Save(outputStream, encoder, parameters);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bitmap.Save(outputStream, ImageFormat.Jpeg);
|
||||
}
|
||||
|
||||
outputBytes = outputStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
width = targetWidth;
|
||||
height = targetHeight;
|
||||
}
|
||||
|
||||
if (outputBytes.Length > HanvonMaxBytes)
|
||||
{
|
||||
note = "too_large_after_prepare";
|
||||
return false;
|
||||
}
|
||||
|
||||
preparedBase64 = Convert.ToBase64String(outputBytes);
|
||||
note = "upscaled";
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsCompleteJpeg(byte[] bytes)
|
||||
{
|
||||
if (bytes == null || bytes.Length < 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes[0] != 0xFF || bytes[1] != 0xD8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = bytes.Length - 2; i >= 0; i--)
|
||||
{
|
||||
if (bytes[i] == 0xFF && bytes[i + 1] == 0xD9)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ImageCodecInfo GetJpegEncoder()
|
||||
{
|
||||
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
|
||||
foreach (var codec in codecs)
|
||||
{
|
||||
if (string.Equals(codec.MimeType, "image/jpeg", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return codec;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int GetIntSetting(string key, int defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var raw = ConfigurationManager.AppSettings[key];
|
||||
if (string.IsNullOrWhiteSpace(raw)) return defaultValue;
|
||||
return int.TryParse(raw.Trim(), out var v) && v > 0 ? v : defaultValue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace HanvonF710XAttendanceService
|
||||
{
|
||||
internal enum DbToDeviceTemplateFormat
|
||||
{
|
||||
Empty,
|
||||
NedoXml,
|
||||
HanvonJpegFace,
|
||||
HanvonRecord
|
||||
}
|
||||
|
||||
internal enum NedoPhotoExtractResult
|
||||
{
|
||||
Found,
|
||||
Missing,
|
||||
Invalid
|
||||
}
|
||||
|
||||
internal sealed class DbToDeviceUploadPlan
|
||||
{
|
||||
public DbToDeviceTemplateFormat Format { get; set; } = DbToDeviceTemplateFormat.Empty;
|
||||
public string PushMode { get; set; }
|
||||
public string Payload { get; set; }
|
||||
public bool UseProfileVerification { get; set; }
|
||||
public NedoPhotoExtractResult? NedoPhotoStatus { get; set; }
|
||||
public int PhotoByteLength { get; set; }
|
||||
public int PhotoBase64Length { get; set; }
|
||||
public int SourceBlobLength { get; set; }
|
||||
public string PrepareNote { get; set; }
|
||||
public int PreparedWidth { get; set; }
|
||||
public int PreparedHeight { get; set; }
|
||||
public string PhotoSourceLabel { get; set; }
|
||||
public string PhotoSourceUrl { get; set; }
|
||||
|
||||
/// <param name="hrmsEmployeeId">employees.id used for portal photo URL. Must NOT be the device serial.</param>
|
||||
public bool TryResolveEmployeePhoto(string hrmsEmployeeId, out string error)
|
||||
{
|
||||
error = null;
|
||||
bool hadNedoPayload = HasUploadPayload;
|
||||
|
||||
if (Format != DbToDeviceTemplateFormat.NedoXml || !EmployeePhotoSourceSettings.IsEnabled())
|
||||
{
|
||||
if (Format == DbToDeviceTemplateFormat.NedoXml && HasUploadPayload)
|
||||
{
|
||||
PhotoSourceLabel = "NEDO_XML";
|
||||
}
|
||||
return HasUploadPayload;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(hrmsEmployeeId))
|
||||
{
|
||||
error = "hrms_employee_id_missing";
|
||||
if (hadNedoPayload)
|
||||
{
|
||||
PhotoSourceLabel = "NEDO_XML";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HrmsEmployeePhotoClient.TryDownloadEmployeePhoto(hrmsEmployeeId, out HrmsEmployeePhotoDownloadResult portalPhoto))
|
||||
{
|
||||
Payload = Convert.ToBase64String(portalPhoto.ImageBytes);
|
||||
PhotoByteLength = portalPhoto.ImageBytes.Length;
|
||||
PhotoBase64Length = portalPhoto.Base64Length;
|
||||
PhotoSourceLabel = "HRMS_PORTAL";
|
||||
PhotoSourceUrl = portalPhoto.Url;
|
||||
PreparedWidth = portalPhoto.Width;
|
||||
PreparedHeight = portalPhoto.Height;
|
||||
return true;
|
||||
}
|
||||
|
||||
error = portalPhoto?.Error;
|
||||
if (hadNedoPayload)
|
||||
{
|
||||
PhotoSourceLabel = "NEDO_XML";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryPrepareFacePayload(out string error)
|
||||
{
|
||||
error = null;
|
||||
if (Format != DbToDeviceTemplateFormat.NedoXml && Format != DbToDeviceTemplateFormat.HanvonJpegFace)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Payload))
|
||||
{
|
||||
error = "empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NedoPhotoPreprocessor.TryPrepareForHanvon(Payload, out string prepared, out int width, out int height, out string note))
|
||||
{
|
||||
error = note ?? "prepare_failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
Payload = prepared;
|
||||
PrepareNote = note;
|
||||
PreparedWidth = width;
|
||||
PreparedHeight = height;
|
||||
PhotoBase64Length = prepared?.Length ?? 0;
|
||||
PhotoByteLength = NedoTemplateConverter.GetDecodedJpegByteLength(prepared);
|
||||
return true;
|
||||
}
|
||||
|
||||
public string FormatLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
case DbToDeviceTemplateFormat.NedoXml: return "NEDO_XML";
|
||||
case DbToDeviceTemplateFormat.HanvonJpegFace: return "HANVON_JPEG";
|
||||
case DbToDeviceTemplateFormat.HanvonRecord: return "HANVON_RECORD";
|
||||
default: return "EMPTY";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasUploadPayload => !string.IsNullOrWhiteSpace(Payload);
|
||||
|
||||
public static DbToDeviceUploadPlan FromBlob(byte[] blob)
|
||||
{
|
||||
var plan = new DbToDeviceUploadPlan();
|
||||
plan.SourceBlobLength = blob?.Length ?? 0;
|
||||
if (blob == null || blob.Length == 0)
|
||||
{
|
||||
plan.Format = DbToDeviceTemplateFormat.Empty;
|
||||
return plan;
|
||||
}
|
||||
|
||||
if (blob.Length >= 3 && blob[0] == 0xFF && blob[1] == 0xD8 && blob[2] == 0xFF)
|
||||
{
|
||||
plan.Format = DbToDeviceTemplateFormat.HanvonJpegFace;
|
||||
plan.PushMode = "face";
|
||||
plan.Payload = Convert.ToBase64String(blob);
|
||||
plan.PhotoByteLength = blob.Length;
|
||||
plan.UseProfileVerification = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
string text = Encoding.UTF8.GetString(blob).Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
plan.Format = DbToDeviceTemplateFormat.Empty;
|
||||
return plan;
|
||||
}
|
||||
|
||||
if (NedoTemplateConverter.IsNedoXml(text))
|
||||
{
|
||||
plan.Format = DbToDeviceTemplateFormat.NedoXml;
|
||||
plan.PushMode = "face";
|
||||
plan.UseProfileVerification = true;
|
||||
|
||||
string photo;
|
||||
NedoPhotoExtractResult photoResult = NedoTemplateConverter.TryExtractPhoto(text, out photo);
|
||||
plan.NedoPhotoStatus = photoResult;
|
||||
if (photoResult == NedoPhotoExtractResult.Found)
|
||||
{
|
||||
plan.Payload = photo;
|
||||
plan.PhotoBase64Length = photo?.Length ?? 0;
|
||||
plan.PhotoByteLength = NedoTemplateConverter.GetDecodedJpegByteLength(photo);
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
string normalized = HanvonHttpApiClient.NormalizeFaceTemplatePayload(blob, out string pushMode);
|
||||
plan.Payload = normalized;
|
||||
plan.PushMode = pushMode;
|
||||
plan.Format = string.Equals(pushMode, "face", StringComparison.OrdinalIgnoreCase)
|
||||
? DbToDeviceTemplateFormat.HanvonJpegFace
|
||||
: DbToDeviceTemplateFormat.HanvonRecord;
|
||||
plan.UseProfileVerification = plan.Format == DbToDeviceTemplateFormat.HanvonJpegFace;
|
||||
if (plan.Format == DbToDeviceTemplateFormat.HanvonJpegFace && !string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
plan.PhotoByteLength = NedoTemplateConverter.GetDecodedJpegByteLength(normalized);
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NedoTemplateConverter
|
||||
{
|
||||
private static readonly Regex PhotoAttributeRegex = new Regex(
|
||||
@"photo\s*=\s*""([^""]*)""",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
public static bool IsNedoXml(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasPhoto = text.IndexOf("photo=\"", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| text.IndexOf("photo =", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
if (!hasPhoto)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasFaceData = text.IndexOf("face_data=\"", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
bool hasCheckTypeFace = text.IndexOf("check_type=\"face\"", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
bool hasIdAttribute = Regex.IsMatch(text, @"\bid\s*=\s*""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
return hasFaceData || hasCheckTypeFace || hasIdAttribute;
|
||||
}
|
||||
|
||||
public static NedoPhotoExtractResult TryExtractPhoto(string nedoText, out string photoBase64)
|
||||
{
|
||||
photoBase64 = null;
|
||||
if (string.IsNullOrWhiteSpace(nedoText))
|
||||
{
|
||||
return NedoPhotoExtractResult.Missing;
|
||||
}
|
||||
|
||||
int photoAttrIndex = FindPhotoAttributeIndex(nedoText, out int valueStart);
|
||||
if (photoAttrIndex < 0)
|
||||
{
|
||||
return NedoPhotoExtractResult.Missing;
|
||||
}
|
||||
|
||||
if (valueStart >= nedoText.Length)
|
||||
{
|
||||
return NedoPhotoExtractResult.Missing;
|
||||
}
|
||||
|
||||
int faceDataIndex = nedoText.IndexOf("\"face_data", valueStart, StringComparison.OrdinalIgnoreCase);
|
||||
if (faceDataIndex < 0)
|
||||
{
|
||||
faceDataIndex = nedoText.IndexOf("\" face_data", valueStart, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
int valueEnd;
|
||||
if (faceDataIndex > valueStart)
|
||||
{
|
||||
valueEnd = faceDataIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueEnd = nedoText.IndexOf('"', valueStart);
|
||||
}
|
||||
|
||||
if (valueEnd <= valueStart)
|
||||
{
|
||||
Match match = PhotoAttributeRegex.Match(nedoText);
|
||||
if (!match.Success || match.Groups.Count < 2)
|
||||
{
|
||||
return NedoPhotoExtractResult.Missing;
|
||||
}
|
||||
|
||||
return ValidateAndNormalizePhoto(match.Groups[1].Value, out photoBase64);
|
||||
}
|
||||
|
||||
string raw = nedoText.Substring(valueStart, valueEnd - valueStart);
|
||||
return ValidateAndNormalizePhoto(raw, out photoBase64);
|
||||
}
|
||||
|
||||
private static int FindPhotoAttributeIndex(string text, out int valueStart)
|
||||
{
|
||||
valueStart = -1;
|
||||
Match spaced = Regex.Match(text, @"photo\s*=\s*""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
if (!spaced.Success)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
valueStart = spaced.Index + spaced.Length;
|
||||
return spaced.Index;
|
||||
}
|
||||
|
||||
private static NedoPhotoExtractResult ValidateAndNormalizePhoto(string raw, out string photoBase64)
|
||||
{
|
||||
photoBase64 = null;
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return NedoPhotoExtractResult.Missing;
|
||||
}
|
||||
|
||||
raw = raw.Trim();
|
||||
if (raw.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
int comma = raw.IndexOf(',');
|
||||
if (comma >= 0)
|
||||
{
|
||||
raw = raw.Substring(comma + 1);
|
||||
}
|
||||
}
|
||||
|
||||
raw = raw.Replace("\r", "").Replace("\n", "").Trim();
|
||||
if (!IsValidJpegBase64(raw))
|
||||
{
|
||||
return NedoPhotoExtractResult.Invalid;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
byte[] decoded = Convert.FromBase64String(raw);
|
||||
if (!NedoPhotoPreprocessor.IsCompleteJpeg(decoded))
|
||||
{
|
||||
return NedoPhotoExtractResult.Invalid;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return NedoPhotoExtractResult.Invalid;
|
||||
}
|
||||
|
||||
photoBase64 = raw;
|
||||
return NedoPhotoExtractResult.Found;
|
||||
}
|
||||
|
||||
public static bool IsValidJpegBase64(string base64)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(base64))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
byte[] decoded = Convert.FromBase64String(base64);
|
||||
return decoded.Length >= 3
|
||||
&& decoded[0] == 0xFF
|
||||
&& decoded[1] == 0xD8
|
||||
&& decoded[2] == 0xFF;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetDecodedJpegByteLength(string base64)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(base64))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Convert.FromBase64String(base64).Length;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UsesFaceField(DbToDeviceUploadPlan plan)
|
||||
{
|
||||
return plan != null
|
||||
&& string.Equals(plan.PushMode, "face", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static bool UsesRecordField(DbToDeviceUploadPlan plan)
|
||||
{
|
||||
return plan != null
|
||||
&& string.Equals(plan.PushMode, "record", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace HanvonF710XAttendanceService
|
||||
{
|
||||
internal static class NedoTemplateConverterTests
|
||||
{
|
||||
private const string SampleJpegBase64 =
|
||||
"/9j/4AAQSkZJRgABAQEAOABkAAD/2wBDAA0JCgsKCA0LCgsODg0PEyAVExISEyccHhcgLikxMC4pLSwzOko+MzZGNywtQFdBRkxOUlNSMj5aYVpQYEpRUk//2wBDAQ4ODhMREyYVFSZPNS01T09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0//wAARCACgAHgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAGfAP/Z";
|
||||
|
||||
public static int RunAll()
|
||||
{
|
||||
int failed = 0;
|
||||
failed += Run("1 detect NEDO XML", TestDetectNedoXml);
|
||||
failed += Run("2 extract valid NEDO photo", TestExtractValidNedoPhoto);
|
||||
failed += Run("3 missing NEDO photo", TestMissingNedoPhoto);
|
||||
failed += Run("4 invalid NEDO photo", TestInvalidNedoPhoto);
|
||||
failed += Run("5 NEDO plan uses face not record", TestNedoPlanUsesFace);
|
||||
failed += Run("6 Hanvon-native template uses record", TestHanvonNativeUsesRecord);
|
||||
failed += Run("7 NEDO plan uses profile verification", TestNedoUsesProfileVerification);
|
||||
failed += Run("8 employee 5 style NEDO blob", TestEmployee5StyleBlob);
|
||||
failed += Run("9 prepare upscales small NEDO photo", TestPrepareUpscalesSmallPhoto);
|
||||
return failed;
|
||||
}
|
||||
|
||||
private static int Run(string name, Func<bool> test)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!test())
|
||||
{
|
||||
Console.WriteLine("FAIL: " + name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Console.WriteLine("PASS: " + name);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("FAIL: " + name + " err=" + ex.Message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TestDetectNedoXml()
|
||||
{
|
||||
string xml = BuildNedoXml("5", SampleJpegBase64);
|
||||
return NedoTemplateConverter.IsNedoXml(xml);
|
||||
}
|
||||
|
||||
private static bool TestExtractValidNedoPhoto()
|
||||
{
|
||||
string xml = BuildNedoXml("5", SampleJpegBase64);
|
||||
NedoPhotoExtractResult result = NedoTemplateConverter.TryExtractPhoto(xml, out string photo);
|
||||
return result == NedoPhotoExtractResult.Found
|
||||
&& photo == SampleJpegBase64
|
||||
&& NedoTemplateConverter.GetDecodedJpegByteLength(photo) > 0;
|
||||
}
|
||||
|
||||
private static bool TestMissingNedoPhoto()
|
||||
{
|
||||
string xml = "id=\"5\" name=\"\" check_type=\"face\" face_data=\"abc\"";
|
||||
NedoPhotoExtractResult result = NedoTemplateConverter.TryExtractPhoto(xml, out string photo);
|
||||
return result == NedoPhotoExtractResult.Missing && photo == null;
|
||||
}
|
||||
|
||||
private static bool TestInvalidNedoPhoto()
|
||||
{
|
||||
string xml = BuildNedoXml("5", "not-valid-base64!!!");
|
||||
NedoPhotoExtractResult result = NedoTemplateConverter.TryExtractPhoto(xml, out string photo);
|
||||
return result == NedoPhotoExtractResult.Invalid && photo == null;
|
||||
}
|
||||
|
||||
private static bool TestNedoPlanUsesFace()
|
||||
{
|
||||
string xml = BuildNedoXml("5", SampleJpegBase64);
|
||||
var plan = DbToDeviceUploadPlan.FromBlob(Encoding.UTF8.GetBytes(xml));
|
||||
return plan.Format == DbToDeviceTemplateFormat.NedoXml
|
||||
&& NedoTemplateConverter.UsesFaceField(plan)
|
||||
&& !NedoTemplateConverter.UsesRecordField(plan);
|
||||
}
|
||||
|
||||
private static bool TestHanvonNativeUsesRecord()
|
||||
{
|
||||
string record = Convert.ToBase64String(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
|
||||
var plan = DbToDeviceUploadPlan.FromBlob(Encoding.UTF8.GetBytes(record));
|
||||
return plan.Format == DbToDeviceTemplateFormat.HanvonRecord
|
||||
&& NedoTemplateConverter.UsesRecordField(plan)
|
||||
&& !NedoTemplateConverter.UsesFaceField(plan);
|
||||
}
|
||||
|
||||
private static bool TestNedoUsesProfileVerification()
|
||||
{
|
||||
string xml = BuildNedoXml("5", SampleJpegBase64);
|
||||
var plan = DbToDeviceUploadPlan.FromBlob(Encoding.UTF8.GetBytes(xml));
|
||||
return plan.UseProfileVerification;
|
||||
}
|
||||
|
||||
private static bool TestEmployee5StyleBlob()
|
||||
{
|
||||
string xml = "id=\"5\" name=\"\" authority=\"0X11\" card_num=\"0Xffffffff\" calid=\"0\" check_type=\"face\" opendoor_type=\"face\" password=\"\" alg_edition=\"3.1\" sn=\"6753718120000147\"photo=\"" + SampleJpegBase64 + "\"face_data=\"abc\"";
|
||||
var plan = DbToDeviceUploadPlan.FromBlob(Encoding.UTF8.GetBytes(xml));
|
||||
return plan.Format == DbToDeviceTemplateFormat.NedoXml
|
||||
&& plan.HasUploadPayload
|
||||
&& plan.NedoPhotoStatus == NedoPhotoExtractResult.Found;
|
||||
}
|
||||
|
||||
private static bool TestPrepareUpscalesSmallPhoto()
|
||||
{
|
||||
var plan = DbToDeviceUploadPlan.FromBlob(Encoding.UTF8.GetBytes(BuildNedoXml("5", SampleJpegBase64)));
|
||||
if (!plan.TryPrepareFacePayload(out string error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(plan.PrepareNote, "upscaled", StringComparison.OrdinalIgnoreCase)
|
||||
&& plan.PreparedWidth >= NedoPhotoPreprocessor.DefaultMinShortSide
|
||||
&& plan.PreparedHeight >= NedoPhotoPreprocessor.DefaultMinShortSide;
|
||||
}
|
||||
|
||||
private static string BuildNedoXml(string id, string photo)
|
||||
{
|
||||
return "id=\"" + id + "\" name=\"\" check_type=\"face\" photo=\"" + photo + "\" face_data=\"biometric-not-used\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue