using System.Collections.ObjectModel; 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; /// // *.png /// 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 _AngleBracketCollection; private readonly Dictionary _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(); ReadOnlyDictionary keyValuePairs = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(_PropertyConfiguration, dResultsFullGroupDirectory, [_PropertyConfiguration.ResultContent]); foreach (KeyValuePair keyValuePair in keyValuePairs) _FileGroups.Add(keyValuePair.Key, keyValuePair.Value); } public void SetAngleBracketCollection(IPropertyConfiguration propertyConfiguration, string d2ResultsFullGroupDirectory, string sourceDirectory) { _AngleBracketCollection.Clear(); _AngleBracketCollection.AddRange(IResult.GetDirectoryInfoCollection(propertyConfiguration, sourceDirectory, d2ResultsFullGroupDirectory, contentDescription: "n gif file(s) for each face found", singletonDescription: string.Empty, collectionDescription: string.Empty, converted: true)); } public string GetFacePartsDirectory(IPropertyConfiguration propertyConfiguration, string dResultsFullGroupDirectory, Item item, bool includeNameWithoutExtension) { string result; bool angleBracketCollectionAny = _AngleBracketCollection.Count != 0; if (!angleBracketCollectionAny) { if (item.FilePath.DirectoryName is null) throw new NullReferenceException(nameof(item.FilePath.DirectoryName)); SetAngleBracketCollection(propertyConfiguration, dResultsFullGroupDirectory, item.FilePath.DirectoryName); } if (includeNameWithoutExtension) result = Path.Combine(_AngleBracketCollection[0].Replace("<>", _PropertyConfiguration.ResultContent), item.FilePath.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, FileHolder 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, FilePath filePath, List> subFileTuples, List parseExceptions, MappingFromItem mappingFromItem, List 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 dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList(); (_, int directoryIndex) = Shared.Models.Stateless.Methods.IPath.GetDirectoryNameAndIndex(_PropertyConfiguration, filePath); string directory = Path.Combine(_FileGroups[_PropertyConfiguration.ResultContent][directoryIndex], mappingFromItem.FilePath.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(filePath, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); fileInfo = new FileInfo(Path.Combine(directory, $"{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 (!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, FileHolder?, 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, FileHolder? _, 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, 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; } public void CopyFacesAndSaveFaceLandmarkImage(string facePartsCollectionDirectory, MappingFromItem mappingFromItem, List<(Shared.Models.Face Face, FileHolder?, string, bool)> faceCollection) { bool hasNotMapped = GetNotMapped(facePartsCollectionDirectory, faceCollection); string fileName = Path.Combine(facePartsCollectionDirectory, $"{mappingFromItem.FilePath.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); } }