261 lines
11 KiB
C#
261 lines
11 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.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 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<D2_FaceParts>();
|
||
_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<Tuple<string, DateTime>> subFileTuples, List<string> parseExceptions, MappingFromItem mappingFromItem, List<Shared.Models.Face> 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<DateTime> 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);
|
||
}
|
||
|
||
} |