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