377 lines
17 KiB
C#
377 lines
17 KiB
C#
using System.Drawing;
|
||
using System.Drawing.Drawing2D;
|
||
using System.Drawing.Imaging;
|
||
using System.Text.Json;
|
||
using View_by_Distance.Face.Models;
|
||
using View_by_Distance.Metadata.Models;
|
||
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 bool _OverrideForFaceLandmarkImages;
|
||
private readonly EncoderParameters _EncoderParameters;
|
||
private readonly List<string> _AngleBracketCollection;
|
||
private readonly Dictionary<string, string[]> _FileGroups;
|
||
private readonly IPropertyConfiguration _PropertyConfiguration;
|
||
|
||
public D2_FaceParts(IPropertyConfiguration propertyConfiguration, ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension, bool checkDFaceAndUpWriteDates, bool overrideForFaceLandmarkImages)
|
||
{
|
||
_FileGroups = [];
|
||
_ImageCodecInfo = imageCodecInfo;
|
||
_EncoderParameters = encoderParameters;
|
||
_FileNameExtension = filenameExtension;
|
||
_AngleBracketCollection = [];
|
||
_PropertyConfiguration = propertyConfiguration;
|
||
_CheckDFaceAndUpWriteDates = checkDFaceAndUpWriteDates;
|
||
_OverrideForFaceLandmarkImages = overrideForFaceLandmarkImages;
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true });
|
||
return result;
|
||
}
|
||
|
||
public void Update(string dResultsFullGroupDirectory)
|
||
{
|
||
_FileGroups.Clear();
|
||
Dictionary<string, string[]> keyValuePairs = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(_PropertyConfiguration, dResultsFullGroupDirectory, [_PropertyConfiguration.ResultContent]);
|
||
foreach (KeyValuePair<string, string[]> keyValuePair in keyValuePairs)
|
||
_FileGroups.Add(keyValuePair.Key, keyValuePair.Value);
|
||
}
|
||
|
||
public void SetAngleBracketCollection(Configuration configuration, string d2ResultsFullGroupDirectory, string sourceDirectory)
|
||
{
|
||
_AngleBracketCollection.Clear();
|
||
_AngleBracketCollection.AddRange(IResult.GetDirectoryInfoCollection(configuration,
|
||
sourceDirectory,
|
||
d2ResultsFullGroupDirectory,
|
||
contentDescription: "n gif file(s) for each face found",
|
||
singletonDescription: string.Empty,
|
||
collectionDescription: string.Empty,
|
||
converted: true));
|
||
}
|
||
|
||
public string GetFacePartsDirectory(Configuration configuration, string dResultsFullGroupDirectory, Item item, bool includeNameWithoutExtension)
|
||
{
|
||
string result;
|
||
bool angleBracketCollectionAny = _AngleBracketCollection.Count != 0;
|
||
if (!angleBracketCollectionAny)
|
||
{
|
||
if (item.ImageFileHolder.DirectoryName is null)
|
||
throw new NullReferenceException(nameof(item.ImageFileHolder.DirectoryName));
|
||
SetAngleBracketCollection(configuration, dResultsFullGroupDirectory, item.ImageFileHolder.DirectoryName);
|
||
}
|
||
if (includeNameWithoutExtension)
|
||
result = Path.Combine(_AngleBracketCollection[0].Replace("<>", _PropertyConfiguration.ResultContent), item.ImageFileHolder.NameWithoutExtension);
|
||
else
|
||
{
|
||
result = _AngleBracketCollection[0].Replace("<>", $"[{_PropertyConfiguration.ResultContent}]");
|
||
if (!Directory.Exists(result))
|
||
_ = Directory.CreateDirectory(result);
|
||
}
|
||
if (!angleBracketCollectionAny)
|
||
_AngleBracketCollection.Clear();
|
||
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;
|
||
}
|
||
}
|
||
|
||
#pragma warning disable CA1416
|
||
|
||
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.
|
||
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);
|
||
}
|
||
#endif
|
||
// Return the result bitmap.
|
||
return result;
|
||
}
|
||
|
||
private static Bitmap RotateBitmap(Image image, float angle)
|
||
{
|
||
Bitmap result;
|
||
Bitmap bitmap = new(image);
|
||
result = RotateBitmap(bitmap, angle);
|
||
bitmap?.Dispose();
|
||
return result;
|
||
}
|
||
|
||
private void SaveFaceParts(int pointSize, IFileHolder resizedFileHolder, bool saveRotated, List<(Shared.Models.Face, string, string)> collection)
|
||
{
|
||
int x;
|
||
int y;
|
||
double? α;
|
||
int width;
|
||
int height;
|
||
Bitmap rotated;
|
||
foreach ((Shared.Models.Face face, string fileName, string rotatedFileName) in collection)
|
||
{
|
||
if (face.FaceEncoding is null)
|
||
continue;
|
||
try
|
||
{
|
||
using (Image image = Image.FromFile(resizedFileHolder.FullName))
|
||
{
|
||
using Graphics graphic = Graphics.FromImage(image);
|
||
if (face.FaceParts is null || face.FaceParts.Count == 0)
|
||
{
|
||
if (face.Location is null)
|
||
continue;
|
||
width = face.Location.Right - face.Location.Left;
|
||
height = face.Location.Bottom - face.Location.Top;
|
||
graphic.DrawEllipse(Pens.Red, face.Location.Left, face.Location.Top, width, height);
|
||
}
|
||
else
|
||
{
|
||
foreach ((FacePart facePart, FacePoint[] facePoints) in face.FaceParts)
|
||
{
|
||
foreach (FacePoint facePoint in facePoints)
|
||
graphic.DrawEllipse(Pens.GreenYellow, 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();
|
||
graphic.DrawEllipse(Pens.Purple, x - pointSize, y - pointSize, pointSize * 2, pointSize * 2);
|
||
}
|
||
}
|
||
image.Save(fileName, _ImageCodecInfo, _EncoderParameters);
|
||
}
|
||
if (saveRotated && face.FaceParts is not null)
|
||
{
|
||
(_, α) = Shared.Models.Stateless.Methods.IFace.GetEyeα(face.FaceParts);
|
||
if (α is null)
|
||
continue;
|
||
using Image image = Image.FromFile(resizedFileHolder.FullName);
|
||
rotated = RotateBitmap(image, (float)α.Value);
|
||
if (rotated is not null)
|
||
{
|
||
rotated.Save(rotatedFileName, _ImageCodecInfo, _EncoderParameters);
|
||
rotated.Dispose();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception) { }
|
||
}
|
||
}
|
||
|
||
#pragma warning restore CA1416
|
||
|
||
public void SaveFaceLandmarkImages(Configuration configuration, List<Tuple<string, DateTime>> subFileTuples, List<string> parseExceptions, MappingFromItem mappingFromItem, List<Shared.Models.Face> faces, bool saveRotated)
|
||
{
|
||
FileInfo fileInfo;
|
||
bool check = false;
|
||
const int pointSize = 2;
|
||
FileInfo rotatedFileInfo;
|
||
DateTime? dateTime = null;
|
||
long ticks = DateTime.Now.Ticks;
|
||
string deterministicHashCodeKey;
|
||
bool updateDateWhenMatches = false;
|
||
List<(Shared.Models.Face, string, string)> collection = [];
|
||
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();
|
||
(_, int directoryIndex) = Shared.Models.Stateless.Methods.IPath.GetDirectoryNameAndIndex(_PropertyConfiguration.ResultAllInOneSubdirectoryLength, mappingFromItem.ImageFileHolder.Name);
|
||
string directory = Path.Combine(_FileGroups[_PropertyConfiguration.ResultContent][directoryIndex], mappingFromItem.ImageFileHolder.NameWithoutExtension);
|
||
bool directoryExists = Directory.Exists(directory);
|
||
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(mappingFromItem.Id, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution);
|
||
fileInfo = new FileInfo(Path.Combine(directory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{_FileNameExtension}"));
|
||
if (string.IsNullOrEmpty(fileInfo.DirectoryName))
|
||
continue;
|
||
rotatedFileInfo = new FileInfo(Path.Combine(fileInfo.DirectoryName, $"{deterministicHashCodeKey} - R{mappingFromItem.ImageFileHolder.ExtensionLowered}{_FileNameExtension}"));
|
||
collection.Add(new(face, fileInfo.FullName, rotatedFileInfo.FullName));
|
||
if (check)
|
||
continue;
|
||
else if (!directoryExists)
|
||
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 (!directoryExists)
|
||
_ = Directory.CreateDirectory(directory);
|
||
SaveFaceParts(pointSize, mappingFromItem.ResizedFileHolder, saveRotated, collection);
|
||
}
|
||
}
|
||
|
||
#pragma warning disable CA1416
|
||
|
||
private void SaveFaceLandmarkImage(MappingFromItem mappingFromItem, List<(Shared.Models.Face, FileInfo?, string, bool)> faceCollection, string fileName)
|
||
{
|
||
Pen pen;
|
||
using Image image = Image.FromFile(mappingFromItem.ResizedFileHolder.FullName);
|
||
using Graphics graphic = Graphics.FromImage(image);
|
||
foreach ((Shared.Models.Face face, FileInfo? fileInfo, string _, bool _) in faceCollection)
|
||
{
|
||
if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null || face.FaceParts is null || face.FaceParts.Count == 0)
|
||
continue;
|
||
pen = face.Mapping?.MappingFromPerson is null ? Pens.Red : Pens.GreenYellow;
|
||
try
|
||
{
|
||
foreach ((FacePart facePart, FacePoint[] facePoints) in face.FaceParts)
|
||
{
|
||
for (int i = 0; i < facePoints.Length - 1; i++)
|
||
graphic.DrawLine(pen, new Point(facePoints[i].X, facePoints[i].Y), new Point(facePoints[i + 1].X, facePoints[i + 1].Y));
|
||
}
|
||
}
|
||
catch (Exception) { }
|
||
}
|
||
image.Save(fileName, _ImageCodecInfo, _EncoderParameters);
|
||
}
|
||
|
||
#pragma warning restore CA1416
|
||
|
||
private static bool GetNotMapped(string facePartsCollectionDirectory, List<(Shared.Models.Face Face, FileInfo?, string, bool)> faceCollection)
|
||
{
|
||
bool results = false;
|
||
string checkFile;
|
||
foreach ((Shared.Models.Face face, FileInfo? fileInfo, string _, bool _) in faceCollection)
|
||
{
|
||
if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
|
||
continue;
|
||
if (fileInfo is not null)
|
||
{
|
||
checkFile = Path.Combine(facePartsCollectionDirectory, fileInfo.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(fileInfo.FullName, checkFile);
|
||
}
|
||
}
|
||
}
|
||
return results;
|
||
}
|
||
|
||
public void CopyFacesAndSaveFaceLandmarkImage(string facePartsCollectionDirectory, MappingFromItem mappingFromItem, List<(Shared.Models.Face Face, FileInfo?, string, bool)> faceCollection)
|
||
{
|
||
bool hasNotMapped = GetNotMapped(facePartsCollectionDirectory, faceCollection);
|
||
string fileName = Path.Combine(facePartsCollectionDirectory, $"{mappingFromItem.ImageFileHolder.Name}{_FileNameExtension}");
|
||
bool save = faceCollection.Any(l => l.Face.FaceEncoding is not null && l.Face.Location is not null && l.Face.OutputResolution is not null && l.Face.FaceParts is not null && l.Face.FaceParts.Count != 0);
|
||
FileInfo fileInfo = new(fileName);
|
||
if (save && (!fileInfo.Exists || new TimeSpan(DateTime.Now.Ticks - fileInfo.LastWriteTime.Ticks).TotalDays > 10))
|
||
{
|
||
SaveFaceLandmarkImage(mappingFromItem, faceCollection, fileName);
|
||
fileInfo.Refresh();
|
||
}
|
||
if (!hasNotMapped && !fileInfo.Attributes.HasFlag(FileAttributes.Hidden) && (fileInfo.Exists || save))
|
||
File.SetAttributes(fileName, FileAttributes.Hidden);
|
||
}
|
||
|
||
} |