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.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;
///
// *.png
///
public class D2_FaceParts
{
protected readonly string _FileNameExtension;
public string FileNameExtension => _FileNameExtension;
private readonly Serilog.ILogger? _Log;
private readonly ImageCodecInfo _ImageCodecInfo;
private readonly bool _CheckDFaceAndUpWriteDates;
private readonly bool _OverrideForFaceLandmarkImages;
private readonly EncoderParameters _EncoderParameters;
public D2_FaceParts(ImageCodecInfo imageCodecInfo, EncoderParameters encoderParameters, string filenameExtension, bool checkDFaceAndUpWriteDates, bool overrideForFaceLandmarkImages)
{
_ImageCodecInfo = imageCodecInfo;
_EncoderParameters = encoderParameters;
_FileNameExtension = filenameExtension;
_Log = Serilog.Log.ForContext();
_CheckDFaceAndUpWriteDates = checkDFaceAndUpWriteDates;
_OverrideForFaceLandmarkImages = overrideForFaceLandmarkImages;
}
public override string ToString()
{
string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true });
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 a 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 PointF(0, 0),
new PointF(bitmap.Width, 0),
new PointF(bitmap.Width, bitmap.Height),
new PointF(0, bitmap.Height),
};
rotate_at_origin.TransformPoints(points);
float xMinimum, xMaximum, yMinimum, yMaximum;
GetPointBounds(points, out xMinimum, out xMaximum, out yMinimum, out yMaximum);
// Make a 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 || face.Location is null)
continue;
try
{
using (Image image = Image.FromFile(resizedFileHolder.FullName))
{
using Graphics graphic = Graphics.FromImage(image);
if (face.FaceParts is null || !face.FaceParts.Any())
{
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)
{
if (face.Location is null)
continue;
graphic.DrawEllipse(Pens.GreenYellow, face.Location.Left + facePoint.X - pointSize, face.Location.Top + 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();
if (face.Location is null)
continue;
graphic.DrawEllipse(Pens.Purple, face.Location.Left + x - pointSize, face.Location.Top + y - pointSize, pointSize * 2, pointSize * 2);
}
}
image.Save(fileName, _ImageCodecInfo, _EncoderParameters);
}
if (saveRotated && face.FaceParts is not null)
{
α = Shared.Models.Stateless.Methods.IFace.Getα(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(Property.Models.Configuration configuration, string facesDirectory, List> subFileTuples, List parseExceptions, MappingFromItem mappingFromItem, List faceCollection, bool saveRotated)
{
FileInfo fileInfo;
bool check = false;
string parentCheck;
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 = new();
string[] changesFrom = new string[] { nameof(Property.Models.A_Property), nameof(B_Metadata), nameof(C_Resize), nameof(D_Face) };
List dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList();
if (!Directory.Exists(facesDirectory))
_ = Directory.CreateDirectory(facesDirectory);
foreach (Shared.Models.Face face in faceCollection)
{
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, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution);
fileInfo = new FileInfo(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{_FileNameExtension}"));
if (!fileInfo.FullName.Contains(configuration.ResultAllInOne) && !fileInfo.Exists)
{
if (fileInfo.Directory?.Parent is null)
throw new Exception();
parentCheck = Path.Combine(fileInfo.Directory.Parent.FullName, fileInfo.Name);
if (File.Exists(parentCheck))
File.Delete(parentCheck);
}
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 (_OverrideForFaceLandmarkImages)
check = true;
else if (!fileInfo.Exists)
check = true;
else if (saveRotated && !rotatedFileInfo.Exists)
check = true;
else if (_CheckDFaceAndUpWriteDates && dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime)
check = true;
if (check && !updateDateWhenMatches)
{
updateDateWhenMatches = dateTimes.Any() && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime;
dateTime = !updateDateWhenMatches ? null : dateTimes.Max();
}
}
if (check)
SaveFaceParts(pointSize, mappingFromItem.ResizedFileHolder, saveRotated, collection);
}
}