HanvonAttendanceService/NedoPhotoPreprocessor.cs

173 lines
5.6 KiB
C#

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