521 lines
23 KiB
C#
521 lines
23 KiB
C#
using System.Collections.ObjectModel;
|
||
using System.Drawing;
|
||
using System.Drawing.Drawing2D;
|
||
using System.Drawing.Imaging;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using View_by_Distance.Face.Models;
|
||
using View_by_Distance.Metadata.Models;
|
||
using View_by_Distance.Metadata.Models.Stateless.Methods;
|
||
using View_by_Distance.Property.Models;
|
||
using View_by_Distance.Property.Models.Stateless;
|
||
using View_by_Distance.Resize.Models;
|
||
using View_by_Distance.Shared.Models;
|
||
using View_by_Distance.Shared.Models.Properties;
|
||
using View_by_Distance.Shared.Models.Stateless;
|
||
|
||
namespace View_by_Distance.FaceParts.Models;
|
||
|
||
/// <summary>
|
||
// *.png
|
||
/// </summary>
|
||
public class D2_FaceParts
|
||
{
|
||
|
||
protected readonly string _FileNameExtension;
|
||
public string FileNameExtension => _FileNameExtension;
|
||
|
||
private readonly ImageCodecInfo _ImageCodecInfo;
|
||
private readonly bool _CheckDFaceAndUpWriteDates;
|
||
private readonly ConstructorInfo _ConstructorInfo;
|
||
private readonly bool _OverrideForFaceLandmarkImages;
|
||
private readonly EncoderParameters _EncoderParameters;
|
||
private readonly IPropertyConfiguration _PropertyConfiguration;
|
||
private readonly ReadOnlyDictionary<byte, ReadOnlyCollection<string>>[] _ResultContentFileGroups;
|
||
|
||
public D2_FaceParts(IPropertyConfiguration propertyConfiguration, ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension, bool checkDFaceAndUpWriteDates, bool overrideForFaceLandmarkImages)
|
||
{
|
||
_ImageCodecInfo = imageCodecInfo;
|
||
_EncoderParameters = encoderParameters;
|
||
_FileNameExtension = filenameExtension;
|
||
_PropertyConfiguration = propertyConfiguration;
|
||
_CheckDFaceAndUpWriteDates = checkDFaceAndUpWriteDates;
|
||
_OverrideForFaceLandmarkImages = overrideForFaceLandmarkImages;
|
||
_ResultContentFileGroups = [new(new Dictionary<byte, ReadOnlyCollection<string>>())];
|
||
ConstructorInfo? constructorInfo = typeof(PropertyItem).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, [], null) ?? throw new Exception();
|
||
_ConstructorInfo = constructorInfo;
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true });
|
||
return result;
|
||
}
|
||
|
||
public void Update(string dResultsFullGroupDirectory)
|
||
{
|
||
ReadOnlyDictionary<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> keyValuePairs = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(_PropertyConfiguration, dResultsFullGroupDirectory, [_PropertyConfiguration.ResultContent]);
|
||
foreach (KeyValuePair<string, ReadOnlyDictionary<byte, ReadOnlyCollection<string>>> keyValuePair in keyValuePairs)
|
||
{
|
||
if (keyValuePair.Key == _PropertyConfiguration.ResultContent)
|
||
_ResultContentFileGroups[0] = keyValuePair.Value;
|
||
else
|
||
throw new Exception();
|
||
}
|
||
}
|
||
|
||
public void SaveFaceLandmarkImages(string d2ResultsFullGroupDirectory, MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List<Shared.Models.Face> faces)
|
||
{
|
||
bool any = false;
|
||
foreach (Shared.Models.Face face in faces)
|
||
{
|
||
if (face.Location is null || face.FaceEncoding is null || face.FaceParts is null || face.FaceParts.Count == 0)
|
||
continue;
|
||
if (!any)
|
||
any = true;
|
||
}
|
||
if (any)
|
||
SaveAllFaceParts(d2ResultsFullGroupDirectory, mappingFromItem, exifDirectory, faces);
|
||
}
|
||
|
||
private void SaveAllFaceParts(string d2ResultsFullGroupDirectory, MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List<Shared.Models.Face> faces)
|
||
{
|
||
int x;
|
||
int y;
|
||
Brush brush;
|
||
int pointSize;
|
||
bool any = false;
|
||
FaceFile faceFile;
|
||
bool? isDefaultName;
|
||
List<long> personKeys = [];
|
||
List<FaceFile> faceFiles = [];
|
||
StringBuilder stringBuilder = new();
|
||
MappingFromPerson? mappingFromPerson;
|
||
string? maker = IMetadata.GetMaker(exifDirectory);
|
||
string? model = IMetadata.GetModel(exifDirectory);
|
||
MetadataExtractor.GeoLocation? geoLocation = IMetadata.GeoLocation(exifDirectory);
|
||
#pragma warning disable CA1416
|
||
using Image image = Image.FromFile(mappingFromItem.ResizedFileHolder.FullName);
|
||
using Graphics graphics = Graphics.FromImage(image);
|
||
foreach (Shared.Models.Face face in faces)
|
||
{
|
||
if (face.Location is null || face.FaceEncoding is null || face.FaceParts is null || face.FaceParts.Count == 0)
|
||
continue;
|
||
if (!any && face.Mapping?.MappingFromPerson is null)
|
||
any = true;
|
||
mappingFromPerson = face.Mapping?.MappingFromPerson;
|
||
brush = mappingFromPerson is null ? Brushes.Red : Brushes.GreenYellow;
|
||
isDefaultName = mappingFromPerson is null ? null : Shared.Models.Stateless.Methods.IPerson.IsDefaultName(mappingFromPerson);
|
||
if (mappingFromPerson is not null && isDefaultName is not null && !isDefaultName.Value)
|
||
personKeys.Add(mappingFromPerson.PersonKey);
|
||
faceFile = new(face.Mapping?.MappingFromLocation?.AreaPermyriad,
|
||
face.Mapping?.MappingFromLocation?.ConfidencePercent,
|
||
geoLocation?.ToDmsString(),
|
||
face.DateTime,
|
||
face.FaceEncoding,
|
||
face.FaceParts,
|
||
face.Location,
|
||
maker,
|
||
mappingFromPerson,
|
||
model,
|
||
face.OutputResolution);
|
||
faceFiles.Add(faceFile);
|
||
pointSize = GetPointSize(face.FaceParts, defaultPointSize: 2);
|
||
foreach ((FacePart facePart, FacePoint[] facePoints) in face.FaceParts)
|
||
{
|
||
foreach (FacePoint facePoint in facePoints)
|
||
graphics.FillEllipse(brush, facePoint.X - pointSize, facePoint.Y - pointSize, pointSize * 2, pointSize * 2);
|
||
if (facePart == FacePart.Chin)
|
||
continue;
|
||
if (facePoints.Length < 3)
|
||
continue;
|
||
x = (int)(from l in facePoints select l.X).Average();
|
||
y = (int)(from l in facePoints select l.Y).Average();
|
||
graphics.FillEllipse(Brushes.Purple, x - pointSize, y - pointSize, pointSize * 2, pointSize * 2);
|
||
}
|
||
}
|
||
_ = graphics.Save();
|
||
#pragma warning restore CA1416
|
||
string directory = GetSeasonDirectory(d2ResultsFullGroupDirectory, mappingFromItem, any);
|
||
SaveImage(mappingFromItem, directory, image, faceFiles);
|
||
}
|
||
|
||
private int GetPointSize(Dictionary<FacePart, FacePoint[]> faceParts, int defaultPointSize)
|
||
{
|
||
int result;
|
||
FacePoint[]? facePoints;
|
||
if (faceParts.TryGetValue(FacePart.LeftEye, out facePoints))
|
||
result = (int)Math.Ceiling((facePoints.Max(l => l.X) - facePoints.Min(l => l.X)) * .05);
|
||
else
|
||
{
|
||
if (faceParts.TryGetValue(FacePart.RightEye, out facePoints))
|
||
result = (int)Math.Ceiling((facePoints.Max(l => l.X) - facePoints.Min(l => l.X)) * .05);
|
||
else
|
||
result = defaultPointSize;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
private string GetSeasonDirectory(string d2ResultsFullGroupDirectory, MappingFromItem mappingFromItem, bool any)
|
||
{
|
||
string result;
|
||
string minimumDateYear = mappingFromItem.MinimumDateTime.ToString("yyyy");
|
||
DateTime dateTime = mappingFromItem.DateTimeOriginal is null ? mappingFromItem.MinimumDateTime : mappingFromItem.DateTimeOriginal.Value;
|
||
(int season, string seasonName) = Shared.Models.Stateless.Methods.IProperty.GetSeason(dateTime.DayOfYear);
|
||
string year = mappingFromItem.DateTimeOriginal is null ? $"{minimumDateYear[1..]}{minimumDateYear[0]}" : mappingFromItem.DateTimeOriginal.Value.ToString("yyyy");
|
||
string directory = Path.Combine(d2ResultsFullGroupDirectory, $"[{_PropertyConfiguration.ResultContent}]", $"{year}.{season} {seasonName}");
|
||
result = any ? Path.Combine(directory, "---") : Path.Combine(directory, "Complete");
|
||
if (!Directory.Exists(result))
|
||
_ = Directory.CreateDirectory(result);
|
||
return result;
|
||
}
|
||
|
||
private void SaveImage(MappingFromItem mappingFromItem, string directory, Image image, List<FaceFile> faceFiles)
|
||
{
|
||
short type = 2;
|
||
string faceFileJson;
|
||
PropertyItem? propertyItem;
|
||
const int artist = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagArtist; // 315
|
||
string fileName = Path.Combine(directory, $"{mappingFromItem.FilePath.Name}{_FileNameExtension}");
|
||
try
|
||
{
|
||
#pragma warning disable CA1416
|
||
foreach (int propertyId in image.PropertyIdList)
|
||
{
|
||
if (propertyId == MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagOrientation)
|
||
continue;
|
||
image.RemovePropertyItem(propertyId);
|
||
}
|
||
faceFileJson = JsonSerializer.Serialize(faceFiles.ToArray(), FaceFileCollectionGenerationContext.Default.FaceFileArray);
|
||
propertyItem = IProperty.GetPropertyItem(_ConstructorInfo, artist, type, faceFileJson);
|
||
image.SetPropertyItem(propertyItem);
|
||
image.Save(fileName, _ImageCodecInfo, _EncoderParameters);
|
||
#pragma warning restore CA1416
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (ex is not null && !string.IsNullOrEmpty(fileName) && File.Exists(fileName))
|
||
File.Delete(fileName);
|
||
faceFileJson = JsonSerializer.Serialize(faceFiles.ToArray(), FaceFileCollectionGenerationContext.Default.FaceFileArray);
|
||
if (!string.IsNullOrEmpty(faceFileJson))
|
||
File.WriteAllText($"{fileName}.json", faceFileJson);
|
||
}
|
||
}
|
||
|
||
public void SaveFaceLandmarkImages(Configuration configuration, string d2ResultsFullGroupDirectory, FilePath filePath, List<Tuple<string, DateTime>> subFileTuples, List<string> parseExceptions, MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List<Shared.Models.Face> faces, bool saveRotated)
|
||
{
|
||
FileInfo fileInfo;
|
||
bool check = false;
|
||
FileInfo rotatedFileInfo;
|
||
DateTime? dateTime = null;
|
||
long ticks = DateTime.Now.Ticks;
|
||
string deterministicHashCodeKey;
|
||
bool updateDateWhenMatches = false;
|
||
List<(Shared.Models.Face, string, string)> collection = [];
|
||
string fileName = mappingFromItem.FilePath.NameWithoutExtension;
|
||
string[] changesFrom = [nameof(A_Property), nameof(B_Metadata), nameof(C_Resize), nameof(D_Face)];
|
||
List<DateTime> dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList();
|
||
CombinedEnumAndIndex cei = Shared.Models.Stateless.Methods.IPath.GetCombinedEnumAndIndex(_PropertyConfiguration, filePath);
|
||
string directory = _ResultContentFileGroups[0][cei.Enum][cei.Index];
|
||
DirectoryInfo directoryInfo = new(Path.Combine(directory, mappingFromItem.FilePath.NameWithoutExtension));
|
||
MoveIf(fileName, cei, directory, directoryInfo);
|
||
foreach (Shared.Models.Face face in faces)
|
||
{
|
||
if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
|
||
{
|
||
collection.Add(new(face, string.Empty, string.Empty));
|
||
continue;
|
||
}
|
||
deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(filePath, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution);
|
||
fileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{deterministicHashCodeKey}{mappingFromItem.FilePath.ExtensionLowered}{_FileNameExtension}"));
|
||
if (string.IsNullOrEmpty(fileInfo.DirectoryName))
|
||
continue;
|
||
rotatedFileInfo = new FileInfo(Path.Combine(fileInfo.DirectoryName, $"{deterministicHashCodeKey} - R{mappingFromItem.FilePath.ExtensionLowered}{_FileNameExtension}"));
|
||
collection.Add(new(face, fileInfo.FullName, rotatedFileInfo.FullName));
|
||
if (check)
|
||
continue;
|
||
else if (!directoryInfo.Exists)
|
||
check = true;
|
||
else if (_OverrideForFaceLandmarkImages)
|
||
check = true;
|
||
else if (!fileInfo.Exists)
|
||
check = true;
|
||
else if (saveRotated && !rotatedFileInfo.Exists)
|
||
check = true;
|
||
else if (_CheckDFaceAndUpWriteDates && dateTimes.Count != 0 && dateTimes.Max() > fileInfo.LastWriteTime)
|
||
check = true;
|
||
if (check && !updateDateWhenMatches)
|
||
{
|
||
updateDateWhenMatches = dateTimes.Count != 0 && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime;
|
||
dateTime = !updateDateWhenMatches ? null : dateTimes.Max();
|
||
}
|
||
}
|
||
if (check)
|
||
{
|
||
if (!directoryInfo.Exists)
|
||
_ = Directory.CreateDirectory(directoryInfo.FullName);
|
||
SaveFaceParts(mappingFromItem, exifDirectory, collection);
|
||
if (saveRotated)
|
||
SaveRotated(mappingFromItem, collection);
|
||
}
|
||
}
|
||
|
||
private static void MoveIf(string fileName, CombinedEnumAndIndex cei, string directory, DirectoryInfo directoryInfo)
|
||
{
|
||
string[] segments = directory.Split(cei.Combined);
|
||
string? checkDirectory = segments.Length == 1 ?
|
||
Path.Combine(segments[0], $"{cei.Combined[2..]}") :
|
||
segments.Length == 2 ?
|
||
$"{segments[0]}{cei.Combined[2..]}{segments[1]}" :
|
||
null;
|
||
if (checkDirectory is not null && Directory.Exists(checkDirectory))
|
||
{
|
||
string checkFile = Path.Combine(checkDirectory, fileName);
|
||
if (Directory.Exists(checkFile))
|
||
{
|
||
Directory.Move(checkFile, directoryInfo.FullName);
|
||
directoryInfo.Refresh();
|
||
}
|
||
}
|
||
}
|
||
|
||
private static Bitmap RotateBitmap(Image image, float angle)
|
||
{
|
||
Bitmap result;
|
||
#pragma warning disable CA1416
|
||
Bitmap bitmap = new(image);
|
||
result = RotateBitmap(bitmap, angle);
|
||
bitmap?.Dispose();
|
||
#pragma warning restore CA1416
|
||
return result;
|
||
}
|
||
|
||
private static Bitmap RotateBitmap(Bitmap bitmap, float angle)
|
||
{
|
||
Bitmap result;
|
||
#if Linux
|
||
throw new Exception("Built on Linux!");
|
||
#elif OSX
|
||
throw new Exception("Built on macOS!");
|
||
#elif Windows
|
||
// Make save Matrix to represent rotation
|
||
// by this angle.
|
||
#pragma warning disable CA1416
|
||
Matrix rotate_at_origin = new();
|
||
rotate_at_origin.Rotate(angle);
|
||
|
||
// Rotate the image's corners to see how big
|
||
// it will be after rotation.
|
||
PointF[] points =
|
||
[
|
||
new(0, 0),
|
||
new(bitmap.Width, 0),
|
||
new(bitmap.Width, bitmap.Height),
|
||
new(0, bitmap.Height),
|
||
];
|
||
rotate_at_origin.TransformPoints(points);
|
||
float xMinimum, xMaximum, yMinimum, yMaximum;
|
||
GetPointBounds(points, out xMinimum, out xMaximum, out yMinimum, out yMaximum);
|
||
|
||
// Make save bitmap to hold the rotated result.
|
||
int wid = (int)Math.Round(xMaximum - xMinimum);
|
||
int hgt = (int)Math.Round(yMaximum - yMinimum);
|
||
result = new Bitmap(wid, hgt);
|
||
|
||
// Create the real rotation transformation.
|
||
Matrix rotate_at_center = new();
|
||
rotate_at_center.RotateAt(angle,
|
||
new PointF(wid / 2f, hgt / 2f));
|
||
|
||
// Draw the image onto the new bitmap rotated.
|
||
using (Graphics gr = Graphics.FromImage(result))
|
||
{
|
||
// Use smooth image interpolation.
|
||
gr.InterpolationMode = InterpolationMode.High;
|
||
|
||
// Clear with the color in the image's upper left corner.
|
||
gr.Clear(bitmap.GetPixel(0, 0));
|
||
|
||
// For debugging. (It's easier to see the background.)
|
||
// gr.Clear(Color.LightBlue);
|
||
|
||
// Set up the transformation to rotate.
|
||
gr.Transform = rotate_at_center;
|
||
|
||
// Draw the image centered on the bitmap.
|
||
int x = (wid - bitmap.Width) / 2;
|
||
int y = (hgt - bitmap.Height) / 2;
|
||
gr.DrawImage(bitmap, x, y);
|
||
}
|
||
#pragma warning restore CA1416
|
||
#endif
|
||
// Return the result bitmap.
|
||
return result;
|
||
}
|
||
|
||
private static void GetPointBounds(PointF[] points, out float xMinimum, out float xMaximum, out float yMinimum, out float yMaximum)
|
||
{
|
||
xMinimum = points[0].X;
|
||
xMaximum = xMinimum;
|
||
yMinimum = points[0].Y;
|
||
yMaximum = yMinimum;
|
||
foreach (PointF point in points)
|
||
{
|
||
if (xMinimum > point.X)
|
||
xMinimum = point.X;
|
||
if (xMaximum < point.X)
|
||
xMaximum = point.X;
|
||
if (yMinimum > point.Y)
|
||
yMinimum = point.Y;
|
||
if (yMaximum < point.Y)
|
||
yMaximum = point.Y;
|
||
}
|
||
}
|
||
|
||
private static bool GetNotMapped(string facePartsCollectionDirectory, List<(Shared.Models.Face Face, FileHolder?, string, bool)> faceCollection)
|
||
{
|
||
bool results = false;
|
||
string checkFile;
|
||
foreach ((Shared.Models.Face face, FileHolder? fileHolder, string _, bool _) in faceCollection)
|
||
{
|
||
if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
|
||
continue;
|
||
if (fileHolder is not null)
|
||
{
|
||
checkFile = Path.Combine(facePartsCollectionDirectory, fileHolder.Name);
|
||
if (face.Mapping?.MappingFromPerson is not null)
|
||
{
|
||
if (File.Exists(checkFile))
|
||
File.Delete(checkFile);
|
||
}
|
||
else if (face.Mapping?.MappingFromFilterPre.InSkipCollection is not null && face.Mapping.MappingFromFilterPre.InSkipCollection.Value)
|
||
{
|
||
if (File.Exists(checkFile))
|
||
File.Delete(checkFile);
|
||
}
|
||
else
|
||
{
|
||
if (!results)
|
||
results = true;
|
||
if (!File.Exists(checkFile))
|
||
File.Copy(fileHolder.FullName, checkFile);
|
||
}
|
||
}
|
||
}
|
||
return results;
|
||
}
|
||
|
||
private void SaveImage(string fileName, Image image, FaceFile faceFile)
|
||
{
|
||
short type = 2;
|
||
string faceFileJson;
|
||
PropertyItem? propertyItem;
|
||
const int artist = MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagArtist; // 315
|
||
try
|
||
{
|
||
#pragma warning disable CA1416
|
||
foreach (int propertyId in image.PropertyIdList)
|
||
{
|
||
if (propertyId == MetadataExtractor.Formats.Exif.ExifDirectoryBase.TagOrientation)
|
||
continue;
|
||
image.RemovePropertyItem(propertyId);
|
||
}
|
||
faceFileJson = JsonSerializer.Serialize(faceFile, FaceFileGenerationContext.Default.FaceFile);
|
||
propertyItem = IProperty.GetPropertyItem(_ConstructorInfo, artist, type, faceFileJson);
|
||
image.SetPropertyItem(propertyItem);
|
||
image.Save(fileName, _ImageCodecInfo, _EncoderParameters);
|
||
#pragma warning restore CA1416
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (ex is not null && !string.IsNullOrEmpty(fileName) && File.Exists(fileName))
|
||
File.Delete(fileName);
|
||
faceFileJson = JsonSerializer.Serialize(faceFile, FaceFileGenerationContext.Default.FaceFile);
|
||
if (!string.IsNullOrEmpty(faceFileJson))
|
||
File.WriteAllText($"{fileName}.json", faceFileJson);
|
||
}
|
||
}
|
||
|
||
private void SaveRotated(MappingFromItem mappingFromItem, List<(Shared.Models.Face, string, string)> collection)
|
||
{
|
||
double? α;
|
||
Bitmap rotated;
|
||
foreach ((Shared.Models.Face face, string _, string rotatedFileName) in collection)
|
||
{
|
||
#pragma warning disable CA1416
|
||
if (face.FaceParts is null)
|
||
continue;
|
||
(_, α) = Shared.Models.Stateless.Methods.IFace.GetEyeα(face.FaceParts);
|
||
if (α is null)
|
||
continue;
|
||
using Image image = Image.FromFile(mappingFromItem.ResizedFileHolder.FullName);
|
||
rotated = RotateBitmap(image, (float)α.Value);
|
||
if (rotated is not null)
|
||
{
|
||
rotated.Save(rotatedFileName, _ImageCodecInfo, _EncoderParameters);
|
||
rotated.Dispose();
|
||
}
|
||
#pragma warning restore CA1416
|
||
}
|
||
}
|
||
|
||
private void SaveFaceParts(MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List<(Shared.Models.Face, string, string)> collection)
|
||
{
|
||
int x;
|
||
int y;
|
||
Brush brush;
|
||
int pointSize;
|
||
FaceFile faceFile;
|
||
MappingFromPerson? mappingFromPerson;
|
||
string? maker = IMetadata.GetMaker(exifDirectory);
|
||
string? model = IMetadata.GetModel(exifDirectory);
|
||
MetadataExtractor.GeoLocation? geoLocation = IMetadata.GeoLocation(exifDirectory);
|
||
foreach ((Shared.Models.Face face, string fileName, string _) in collection)
|
||
{
|
||
try
|
||
{
|
||
#pragma warning disable CA1416
|
||
if (face.Location is null || face.FaceEncoding is null || face.FaceParts is null || face.FaceParts.Count == 0)
|
||
continue;
|
||
using Image image = Image.FromFile(mappingFromItem.ResizedFileHolder.FullName);
|
||
mappingFromPerson = face.Mapping?.MappingFromPerson;
|
||
brush = mappingFromPerson is null ? Brushes.Red : Brushes.GreenYellow;
|
||
faceFile = new(face.Mapping?.MappingFromLocation?.AreaPermyriad,
|
||
face.Mapping?.MappingFromLocation?.ConfidencePercent,
|
||
geoLocation?.ToDmsString(),
|
||
face.DateTime,
|
||
face.FaceEncoding,
|
||
face.FaceParts,
|
||
face.Location,
|
||
maker,
|
||
mappingFromPerson,
|
||
model,
|
||
face.OutputResolution);
|
||
using Graphics graphics = Graphics.FromImage(image);
|
||
pointSize = GetPointSize(face.FaceParts, defaultPointSize: 2);
|
||
foreach ((FacePart facePart, FacePoint[] facePoints) in face.FaceParts)
|
||
{
|
||
foreach (FacePoint facePoint in facePoints)
|
||
graphics.FillEllipse(brush, facePoint.X - pointSize, facePoint.Y - pointSize, pointSize * 2, pointSize * 2);
|
||
if (facePart == FacePart.Chin)
|
||
continue;
|
||
if (facePoints.Length < 3)
|
||
continue;
|
||
x = (int)(from l in facePoints select l.X).Average();
|
||
y = (int)(from l in facePoints select l.Y).Average();
|
||
graphics.FillEllipse(Brushes.Purple, x - pointSize, y - pointSize, pointSize * 2, pointSize * 2);
|
||
}
|
||
_ = graphics.Save();
|
||
SaveImage(fileName, image, faceFile);
|
||
#pragma warning restore CA1416
|
||
}
|
||
catch (Exception)
|
||
{
|
||
if (File.Exists(fileName))
|
||
File.Delete(fileName);
|
||
}
|
||
}
|
||
}
|
||
|
||
} |