Introduce IImageCreator interface for image creation, and update ImageCreatorSharp to implement it. Add ImageCreatorAlternate (adapter) and ImageCreatorMapper (runtime selector) classes. Extend PicSettings with ImageCreatorProvider to control backend selection. Update DI registrations and refactor ImageCreationStuff to depend on IImageCreator, enabling backend switching via configuration.
846 lines
34 KiB
C#
846 lines
34 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Drawing.Imaging;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
|
|
|
// Imports System.Threading
|
|
|
|
namespace MaddoShared;
|
|
|
|
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
|
public class ImageCreatorSharp(PicSettings picSettings, ILogger<ImageCreatorSharp> logger) : IImageCreator
|
|
{
|
|
public async Task CreateImageAsync(ImageState imgState, Image logo)
|
|
{
|
|
try
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
logger.LogInformation("File: {FileInfo} Dest: {DirectoryInfo}", imgState.WorkFile, imgState.DestDir);
|
|
PrepareVariables(imgState);
|
|
ExtractExif(imgState);
|
|
|
|
using var g = Image.FromFile(imgState.WorkFile.FullName);
|
|
|
|
// Set extra text
|
|
SetExtraText(g, imgState);
|
|
|
|
// Rotate image according to EXIF
|
|
ApplyRotation(g, imgState);
|
|
|
|
// Force jpeg if option selected
|
|
var thisFormat = g.RawFormat;
|
|
if (picSettings.UsaForzaJpg)
|
|
thisFormat = ImageFormat.Jpeg;
|
|
|
|
PrepareThumbnailSize(g, imgState);
|
|
|
|
using var imgOutputBig = new Bitmap(g, imgState.ThumbSizeBig.Width, imgState.ThumbSizeBig.Height);
|
|
|
|
imgOutputBig.SetResolution(g.HorizontalResolution, g.VerticalResolution);
|
|
|
|
// Create thumbnails
|
|
CreateThumbnails(g, imgState, imgOutputBig, thisFormat);
|
|
|
|
AddText(g, imgState, imgOutputBig);
|
|
|
|
AddLogo(imgOutputBig, logo);
|
|
|
|
SavePhoto(imgOutputBig, imgState, thisFormat);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
|
|
catch (Exception ex)
|
|
{
|
|
var e = ex.Demystify();
|
|
logger.LogError(e, "Error in processing photo {WorkFileName}", imgState.WorkFile.Name);
|
|
}
|
|
}
|
|
|
|
private void ExtractExif(ImageState imgState)
|
|
{
|
|
using var img = SixLabors.ImageSharp.Image.Load(imgState.WorkFile.FullName);
|
|
imgState.Orientation = Orientations.TopLeft;
|
|
|
|
IExifValue<ushort> rotation = null;
|
|
|
|
var exifProfile = img.Metadata?.ExifProfile;
|
|
var found = exifProfile != null && exifProfile.TryGetValue(ExifTag.Orientation, out rotation);
|
|
|
|
if (found)
|
|
{
|
|
var intOrientation = rotation.Value.ToInt32();
|
|
imgState.Orientation = (Orientations)intOrientation;
|
|
}
|
|
|
|
IExifValue<string> date = null;
|
|
var creationFound = exifProfile != null && exifProfile.TryGetValue(ExifTag.DateTimeOriginal, out date);
|
|
if (creationFound)
|
|
{
|
|
var succ = DateTime.TryParseExact(date.Value, "yyyy:MM:dd HH:mm:ss", CultureInfo.InvariantCulture,
|
|
DateTimeStyles.None, out var crDate);
|
|
if (succ)
|
|
{
|
|
imgState.CreationDate = crDate;
|
|
}
|
|
else
|
|
{
|
|
imgState.CreationDate = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
imgState.CreationDate = null;
|
|
}
|
|
}
|
|
|
|
private void ApplyRotation(Image g, ImageState imgState)
|
|
{
|
|
imgState.FotoRuotaADestra = false;
|
|
imgState.FotoRuotaASinistra = false;
|
|
|
|
if (picSettings.UsaRotazioneAutomatica && g.PropertyIdList.Length > 0)
|
|
{
|
|
switch (imgState.Orientation)
|
|
{
|
|
case Orientations.BottomLeft:
|
|
case Orientations.BottomRight:
|
|
case Orientations.LeftTop:
|
|
case Orientations.LftBottom:
|
|
imgState.FotoRuotaASinistra = true;
|
|
break;
|
|
case Orientations.RightBottom:
|
|
case Orientations.RightTop:
|
|
case Orientations.TopLeft:
|
|
case Orientations.TopRight:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (imgState.FotoRuotaASinistra)
|
|
g.RotateFlip(RotateFlipType.Rotate270FlipNone);
|
|
if (imgState.FotoRuotaADestra)
|
|
g.RotateFlip(RotateFlipType.Rotate90FlipNone);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ''' Aggiunge Orario, tempo gara e altri
|
|
/// ''' </summary>
|
|
/// ''' <param name="g">Image</param>
|
|
/// <param name="imgState"></param>
|
|
/// ''' <remarks></remarks>
|
|
private void SetExtraText(Image g, ImageState imgState)
|
|
{
|
|
if (picSettings.UsaOrarioTestoApplicare || picSettings.UsaTempoGaraTestoApplicare ||
|
|
picSettings.UsaOrarioMiniatura || picSettings.TestoMin || picSettings.AggTempoGaraMin ||
|
|
picSettings.AggNumTempMin)
|
|
{
|
|
if (g.PropertyIdList.Length <= 0) return;
|
|
imgState.DataFoto = imgState.CreationDate ?? DateTime.Now;
|
|
imgState.TestoFirma = picSettings.TestoFirmaStart;
|
|
imgState.TestoFirmaV = picSettings.TestoFirmaStartV;
|
|
|
|
if (imgState.DataFoto.Year == 1) return;
|
|
imgState.TestoFirmaPiccola = imgState.DataFoto.ToShortTimeString();
|
|
if (picSettings.UsaOrarioTestoApplicare)
|
|
{
|
|
imgState.TestoFirma +=
|
|
$" {imgState.DataFoto.ToShortDateString()} {imgState.DataFoto.ToLongTimeString()}";
|
|
imgState.TestoFirmaV +=
|
|
$" {imgState.DataFoto.ToShortDateString()} {imgState.DataFoto.ToLongTimeString()}";
|
|
}
|
|
|
|
if (!picSettings.UsaTempoGaraTestoApplicare) return;
|
|
var diff = imgState.DataFoto - imgState.DataPartenzaI;
|
|
imgState.TestoFirma += $" {imgState.TestoOrario}{diff.Hours:00}:{diff.Minutes:00}:{diff.Seconds:00}";
|
|
imgState.TestoFirmaV += $" {imgState.TestoOrario}{diff.Hours:00}:{diff.Minutes:00}:{diff.Seconds:00}";
|
|
}
|
|
else
|
|
{
|
|
imgState.TestoFirma = picSettings.TestoFirmaStart;
|
|
imgState.TestoFirmaV = picSettings.TestoFirmaStartV;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// ''' Prepara diverse variabili azzerandole, elaborandole e prendendole dalle impostazioni
|
|
/// ''' </summary>
|
|
/// ''' <remarks></remarks>
|
|
private void PrepareVariables(ImageState imgState)
|
|
{
|
|
imgState.AlphaScelta = System.Convert.ToInt32((255 * (100 - picSettings.Trasparenza) / (double)100));
|
|
imgState.TestoFirma = "";
|
|
imgState.TestoFirmaV = "";
|
|
imgState.DataPartenzaI = picSettings.DataPartenza;
|
|
imgState.TestoOrario = picSettings.TestoOrario;
|
|
if (imgState.TestoOrario.Length > 0)
|
|
imgState.TestoOrario += " ";
|
|
imgState.TestoFirmaPiccola = "";
|
|
imgState.ThumbSizeSmall = new Size();
|
|
imgState.ThumbSizeBig = new Size();
|
|
imgState.NomeFileSmall = "";
|
|
imgState.NomeFileBig2 = "";
|
|
imgState.NomeFileBig = "";
|
|
imgState.DimensioneStandard = picSettings.DimStandard;
|
|
imgState.DimensioneStandardMiniatura = picSettings.DimStandardMiniatura;
|
|
// nomeFileSmall = Suffisso & NomeFileChild
|
|
// nomeFileBig = NomeFileChild
|
|
imgState.NomeFileSmall = picSettings.Suffisso + imgState.WorkFile.Name;
|
|
imgState.NomeFileBig = imgState.WorkFile.Name;
|
|
// Sanitize file names to avoid invalid characters causing IO errors
|
|
imgState.NomeFileSmall = SanitizeFileName(imgState.NomeFileSmall);
|
|
imgState.NomeFileBig = SanitizeFileName(imgState.NomeFileBig);
|
|
}
|
|
|
|
private static string SanitizeFileName(string fileName)
|
|
{
|
|
if (string.IsNullOrEmpty(fileName)) return fileName;
|
|
var invalid = Path.GetInvalidFileNameChars();
|
|
var sb = new System.Text.StringBuilder(fileName.Length);
|
|
foreach (var ch in fileName)
|
|
{
|
|
sb.Append(Array.IndexOf(invalid, ch) >= 0 ? '_' : ch);
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
private void PrepareThumbnailSize(Image g, ImageState imgState)
|
|
{
|
|
if (g.Width > g.Height)
|
|
{
|
|
imgState.ThumbSizeSmall = CalculateThumbnailSize(g.Width, g.Height, picSettings.LarghezzaSmall, "Larghezza");
|
|
var sizeOrig = new Size(g.Width, g.Height);
|
|
imgState.ThumbSizeBig = sizeOrig;
|
|
}
|
|
else
|
|
{
|
|
imgState.ThumbSizeSmall = CalculateThumbnailSize(g.Width, g.Height, picSettings.AltezzaSmall, "Altezza");
|
|
var sizeOrig = new Size(g.Width, g.Height);
|
|
imgState.ThumbSizeBig = sizeOrig;
|
|
}
|
|
}
|
|
|
|
private void CreateThumbnails(Image sourceImage, ImageState imgState, Bitmap imgOutputBig, ImageFormat format)
|
|
{
|
|
if (!picSettings.CreaMiniature || picSettings.AggiungiScritteMiniature)
|
|
return;
|
|
|
|
PrepareSignatureText(imgState);
|
|
|
|
if (IsSameDirectory(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione))
|
|
UpdateFilenameWithCode(imgState);
|
|
|
|
if (ShouldRenderText())
|
|
CreateThumbnailWithText(sourceImage, imgState, imgOutputBig, format);
|
|
else
|
|
CreateSimpleThumbnail(sourceImage, imgState, format);
|
|
}
|
|
|
|
private void PrepareSignatureText(ImageState imgState)
|
|
{
|
|
if (picSettings.TestoMin)
|
|
imgState.TestoFirmaPiccola = imgState.NomeFileBig;
|
|
else if (picSettings.AggNumTempMin)
|
|
imgState.TestoFirmaPiccola = imgState.NomeFileBig + " ";
|
|
}
|
|
|
|
private bool IsSameDirectory(string dir1, string dir2) =>
|
|
string.Equals(dir1, dir2, StringComparison.OrdinalIgnoreCase);
|
|
|
|
private void UpdateFilenameWithCode(ImageState imgState)
|
|
{
|
|
var name = imgState.NomeFileSmall;
|
|
imgState.NomeFileSmall = name[..^4] + picSettings.Codice + name[^4..];
|
|
}
|
|
|
|
private bool ShouldRenderText() =>
|
|
picSettings.UsaOrarioMiniatura || picSettings.TestoMin || picSettings.AggTempoGaraMin ||
|
|
picSettings.AggNumTempMin;
|
|
|
|
private void CreateSimpleThumbnail(Image image, ImageState imgState, ImageFormat format)
|
|
{
|
|
using var thumbnail = new Bitmap(image, imgState.ThumbSizeSmall.Width, imgState.ThumbSizeSmall.Height);
|
|
thumbnail.Save(Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall), format);
|
|
}
|
|
|
|
private void CreateThumbnailWithText(Image image, ImageState imgState, Bitmap sourceBitmap, ImageFormat format)
|
|
{
|
|
if (imgState.TestoFirmaPiccola.Length == 0)
|
|
{
|
|
CreateSimpleThumbnail(image, imgState, format);
|
|
return;
|
|
}
|
|
|
|
using var imgOutputSmall = (Bitmap)sourceBitmap.Clone();
|
|
using var graphics = Graphics.FromImage(imgOutputSmall);
|
|
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
|
|
|
// Use the user's configured font size directly
|
|
using var font1 = CreateFont(picSettings.IlFont, imgState.DimensioneStandardMiniatura, picSettings.Grassetto);
|
|
var textSize = graphics.MeasureString(imgState.TestoFirmaPiccola, font1);
|
|
|
|
// Adjust font if it's too large for the image dimensions
|
|
// Keep text height under 15% of image height to ensure proper spacing when resized
|
|
// This leaves room for margins and prevents clipping
|
|
int tempFontSize = imgState.DimensioneStandardMiniatura;
|
|
float maxTextHeight = image.Height * 0.15f;
|
|
|
|
while ((textSize.Width > image.Width * 0.95f || textSize.Height > maxTextHeight) && tempFontSize > 5)
|
|
{
|
|
tempFontSize = (tempFontSize > 20) ? tempFontSize - 5 : tempFontSize - 1;
|
|
using var tempFont = CreateFont(picSettings.IlFont, tempFontSize, picSettings.Grassetto);
|
|
textSize = graphics.MeasureString(imgState.TestoFirmaPiccola, tempFont);
|
|
}
|
|
|
|
// Re-measure text with the final font size for accurate positioning
|
|
using var finalFont = CreateFont(picSettings.IlFont, tempFontSize, picSettings.Grassetto);
|
|
var finalTextSize = graphics.MeasureString(imgState.TestoFirmaPiccola, finalFont);
|
|
|
|
SetVerticalPosition(image.Height, finalTextSize.Height, imgState);
|
|
|
|
float xCenter = CalculateHorizontalAlignment(image.Width, finalTextSize.Width);
|
|
using var stringFormat = new StringFormat();
|
|
stringFormat.Alignment = StringAlignment.Center;
|
|
|
|
using var shadowBrush = new SolidBrush(Color.FromArgb(imgState.AlphaScelta, 0, 0, 0));
|
|
using var textBrush = new SolidBrush(Color.FromArgb(imgState.AlphaScelta, picSettings.FontColoreRGB));
|
|
|
|
DrawText(graphics, imgState, xCenter, stringFormat, shadowBrush, textBrush, finalFont);
|
|
|
|
using var finalThumb =
|
|
new Bitmap(imgOutputSmall, imgState.ThumbSizeSmall.Width, imgState.ThumbSizeSmall.Height);
|
|
finalThumb.Save(Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall), format);
|
|
}
|
|
|
|
private Font CreateFont(string fontName, int size, bool bold) =>
|
|
new Font(fontName, size, bold ? FontStyle.Bold : FontStyle.Regular);
|
|
|
|
private int FindBestFontSize(Graphics g, string text, string fontName, int maxSize, bool bold, int maxWidth, int minSize = 5)
|
|
{
|
|
if (maxSize <= minSize) return Math.Max(minSize, maxSize);
|
|
|
|
int low = minSize;
|
|
int high = Math.Max(minSize, maxSize);
|
|
int best = minSize;
|
|
|
|
while (low <= high)
|
|
{
|
|
int mid = (low + high) / 2;
|
|
using var testFont = CreateFont(fontName, mid, bold);
|
|
var measured = g.MeasureString(text, testFont);
|
|
if (measured.Width <= maxWidth)
|
|
{
|
|
best = mid;
|
|
low = mid + 1; // try larger
|
|
}
|
|
else
|
|
{
|
|
high = mid - 1; // too big
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
|
|
private void AddText(Image g, ImageState imgState, Bitmap imgOutputBig)
|
|
{
|
|
using var grPhoto = Graphics.FromImage(imgOutputBig);
|
|
grPhoto.SmoothingMode = SmoothingMode.AntiAlias;
|
|
|
|
// Determine best base font size using a binary search (faster than decremental loop)
|
|
int availableWidth = (int)g.Width;
|
|
int targetBaseSize = imgState.DimensioneStandard > 0 ? imgState.DimensioneStandard : picSettings.DimStandard;
|
|
int bestBaseSize = FindBestFontSize(grPhoto, imgState.TestoFirma ?? string.Empty, picSettings.IlFont, targetBaseSize, picSettings.Grassetto, availableWidth);
|
|
imgState.DimensioneStandard = bestBaseSize;
|
|
|
|
// Decide final drawing size (use DimVert if rotated)
|
|
int drawSize = (imgState.FotoRuotaADestra || imgState.FotoRuotaASinistra) ? picSettings.DimVert : imgState.DimensioneStandard;
|
|
|
|
using var drawFont = CreateFont(picSettings.IlFont, drawSize, picSettings.Grassetto);
|
|
var crSize = grPhoto.MeasureString(imgState.TestoFirma ?? string.Empty, drawFont);
|
|
var larghezzaStandard = Convert.ToInt32(crSize.Width);
|
|
|
|
// Vertical positions
|
|
switch (picSettings.Posizione.ToUpper())
|
|
{
|
|
case "ALTO":
|
|
{
|
|
imgState.YPosFromBottom = picSettings.Margine;
|
|
imgState.YPosFromBottom3 = picSettings.MargVert;
|
|
break;
|
|
}
|
|
|
|
case "BASSO":
|
|
{
|
|
imgState.YPosFromBottom =
|
|
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.Margine / 100.0)));
|
|
imgState.YPosFromBottom3 =
|
|
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.MargVert / 100.0)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
float xCenterOfImg = 0;
|
|
using var strFormat = new StringFormat();
|
|
switch (picSettings.Allineamento.ToUpper())
|
|
{
|
|
case "SINISTRA":
|
|
{
|
|
xCenterOfImg = Convert.ToSingle((picSettings.Margine + (larghezzaStandard / (double)2)));
|
|
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
|
|
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
|
break;
|
|
}
|
|
|
|
case "CENTRO":
|
|
{
|
|
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
|
break;
|
|
}
|
|
|
|
case "DESTRA":
|
|
{
|
|
xCenterOfImg =
|
|
Convert.ToSingle((g.Width - picSettings.Margine - (larghezzaStandard / (double)2)));
|
|
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
|
|
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
|
break;
|
|
}
|
|
}
|
|
|
|
strFormat.Alignment = StringAlignment.Center;
|
|
|
|
using var semiTransBrush2 = new SolidBrush(Color.FromArgb(imgState.AlphaScelta, 0, 0, 0));
|
|
using var semiTransBrush = new SolidBrush(Color.FromArgb(imgState.AlphaScelta, picSettings.FontColoreRGB));
|
|
|
|
// write text (NomeFileBig)
|
|
if (picSettings.TestoNome)
|
|
{
|
|
if (picSettings.NomeData && g.PropertyIdList.Length > 0)
|
|
{
|
|
imgState.DataFoto = imgState.CreationDate ?? DateTime.Now;
|
|
|
|
grPhoto.DrawString((imgState.NomeFileBig + " " + imgState.DataFoto.ToShortDateString()), drawFont,
|
|
semiTransBrush2, new PointF(xCenterOfImg + 1, imgState.YPosFromBottom + 1), strFormat);
|
|
grPhoto.DrawString((imgState.NomeFileBig + " " + imgState.DataFoto.ToShortDateString()), drawFont,
|
|
semiTransBrush, new PointF(xCenterOfImg, imgState.YPosFromBottom), strFormat);
|
|
}
|
|
else
|
|
{
|
|
grPhoto.DrawString(imgState.NomeFileBig, drawFont, semiTransBrush2,
|
|
new PointF(xCenterOfImg + 1, imgState.YPosFromBottom + 1), strFormat);
|
|
grPhoto.DrawString(imgState.NomeFileBig, drawFont, semiTransBrush,
|
|
new PointF(xCenterOfImg, imgState.YPosFromBottom), strFormat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (imgState.FotoRuotaADestra || imgState.FotoRuotaASinistra)
|
|
{
|
|
if (!picSettings.TestoMin)
|
|
{
|
|
grPhoto.DrawString(imgState.TestoFirmaV, drawFont, semiTransBrush2,
|
|
new PointF(xCenterOfImg + 1, imgState.YPosFromBottom3 + 1), strFormat);
|
|
grPhoto.DrawString(imgState.TestoFirmaV, drawFont, semiTransBrush,
|
|
new PointF(xCenterOfImg, imgState.YPosFromBottom3), strFormat);
|
|
}
|
|
|
|
if (picSettings.TestoMin)
|
|
{
|
|
grPhoto.DrawString(imgState.TestoFirmaV, drawFont, semiTransBrush2,
|
|
new PointF(xCenterOfImg + 1, imgState.YPosFromBottom4 + 1), strFormat);
|
|
grPhoto.DrawString(imgState.TestoFirmaV, drawFont, semiTransBrush,
|
|
new PointF(xCenterOfImg, imgState.YPosFromBottom4), strFormat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
grPhoto.DrawString(imgState.TestoFirma, drawFont, semiTransBrush2,
|
|
new PointF(xCenterOfImg + 1, imgState.YPosFromBottom + 1), strFormat);
|
|
grPhoto.DrawString(imgState.TestoFirma, drawFont, semiTransBrush,
|
|
new PointF(xCenterOfImg, imgState.YPosFromBottom), strFormat);
|
|
}
|
|
}
|
|
|
|
if (string.Equals(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione,
|
|
StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
imgState.NomeFileBig2 = imgState.NomeFileBig;
|
|
imgState.NomeFileBig = $"{imgState.NomeFileBig[..^4]}{picSettings.Codice}{imgState.NomeFileBig[^4..]}";
|
|
}
|
|
}
|
|
|
|
private void AddLogo(Bitmap imgOutputBig, Image logo)
|
|
{
|
|
// Skip if no logo provided
|
|
if (logo is null) return;
|
|
|
|
// Load check (use short-circuit &&)
|
|
if (!(picSettings.LogoAggiungi && File.Exists(picSettings.LogoNomeFile))) return;
|
|
|
|
var logoColoreTrasparente = Color.White;
|
|
|
|
// * Load this Bitmap into a new Graphic Object
|
|
using var grWatermark = Graphics.FromImage(imgOutputBig);
|
|
using ImageAttributes imageAttributes = new ImageAttributes();
|
|
|
|
// * The first step replace the background color with one that is transparent (Alpha=0, R=0, G=0, B=0)
|
|
var colorMap = new ColorMap
|
|
{
|
|
// * background this will be the color we search for and replace with transparency
|
|
OldColor = logoColoreTrasparente,
|
|
NewColor = Color.FromArgb(0, 0, 0, 0)
|
|
};
|
|
|
|
var remapTable = new[] { colorMap };
|
|
imageAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);
|
|
|
|
// * The second color manipulation is used to change the opacity by setting the 3rd row and 3rd column to 0.3f
|
|
// Parse transparency safely (default to 100 if parsing fails)
|
|
if (!int.TryParse(picSettings.LogoTrasparenza, out var logoTransparencyValue))
|
|
{
|
|
logoTransparencyValue = 100;
|
|
}
|
|
|
|
var colorMatrixElements = new[]
|
|
{
|
|
new[] { 1.0F, 0.0F, 0.0F, 0.0F, 0.0F }, new[] { 0.0F, 1.0F, 0.0F, 0.0F, 0.0F },
|
|
new[] { 0.0F, 0.0F, 1.0F, 0.0F, 0.0F },
|
|
new[] { 0.0F, 0.0F, 0.0F, System.Convert.ToSingle(logoTransparencyValue) / 100F, 0.0F },
|
|
new[] { 0.0F, 0.0F, 0.0F, 0.0F, 1.0F }
|
|
};
|
|
var wmColorMatrix = new ColorMatrix(colorMatrixElements);
|
|
imageAttributes.SetColorMatrix(wmColorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
|
|
|
|
var fotoLogoH = picSettings.LogoAltezza;
|
|
var fotoLogoW = picSettings.LogoLarghezza;
|
|
var fattoreAlt = logo.Height / (double)fotoLogoH;
|
|
var fattoreLarg = logo.Width / (double)fotoLogoW;
|
|
var nuovaSize = fattoreLarg > fattoreAlt
|
|
? CalculateThumbnailSize(logo.Width, logo.Height, fotoLogoW, "Larghezza")
|
|
: CalculateThumbnailSize(logo.Width, logo.Height, fotoLogoH, "Altezza");
|
|
|
|
// Guard against null/empty LogoMargine and percentage parsing
|
|
var logoMargineStr = picSettings.LogoMargine ?? string.Empty;
|
|
var inPercentualeL = logoMargineStr.EndsWith('%');
|
|
var margineL = 0;
|
|
if (inPercentualeL)
|
|
{
|
|
var trimmed = logoMargineStr.TrimEnd('%');
|
|
if (!int.TryParse(trimmed, out margineL)) margineL = 0;
|
|
}
|
|
else
|
|
{
|
|
if (!int.TryParse(logoMargineStr, out margineL)) margineL = 0;
|
|
}
|
|
var margineUsato =
|
|
inPercentualeL ? System.Convert.ToInt32(imgOutputBig.Height * margineL / (double)100) : margineL;
|
|
|
|
int xPosOfWm = 0;
|
|
int yPosOfWm = 0;
|
|
var logoH = (picSettings.LogoPosizioneH ?? "NESSUNA").ToUpperInvariant();
|
|
var logoV = (picSettings.LogoPosizioneV ?? "NESSUNA").ToUpperInvariant();
|
|
switch (logoH)
|
|
{
|
|
case "SINISTRA":
|
|
case "NESSUNA":
|
|
{
|
|
xPosOfWm = margineUsato;
|
|
break;
|
|
}
|
|
|
|
case "CENTRO":
|
|
{
|
|
xPosOfWm = System.Convert.ToInt32((imgOutputBig.Width - nuovaSize.Width) / (double)2);
|
|
break;
|
|
}
|
|
|
|
case "DESTRA":
|
|
{
|
|
xPosOfWm = ((imgOutputBig.Width - nuovaSize.Width) - margineUsato);
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (logoV)
|
|
{
|
|
case "ALTO":
|
|
case "NESSUNA":
|
|
{
|
|
yPosOfWm = margineUsato;
|
|
break;
|
|
}
|
|
|
|
case "CENTRO":
|
|
{
|
|
yPosOfWm = System.Convert.ToInt32((imgOutputBig.Height - nuovaSize.Height) / (double)2);
|
|
break;
|
|
}
|
|
|
|
case "BASSO":
|
|
{
|
|
yPosOfWm = ((imgOutputBig.Height - nuovaSize.Height) - margineUsato);
|
|
break;
|
|
}
|
|
}
|
|
|
|
grWatermark.DrawImage(logo, new Rectangle(xPosOfWm, yPosOfWm, nuovaSize.Width, nuovaSize.Height), 0, 0,
|
|
logo.Width, logo.Height, GraphicsUnit.Pixel, imageAttributes);
|
|
//grWatermark.Dispose();
|
|
}
|
|
|
|
|
|
private void SavePhoto(Bitmap imgOutputBig, ImageState imgState, ImageFormat thisFormat)
|
|
{
|
|
var fileName = Path.Combine(imgState.DestDir.FullName, imgState.NomeFileBig);
|
|
|
|
using var image1Stream = new MemoryStream();
|
|
if (picSettings.FotoGrandeDimOrigina == false)
|
|
{
|
|
// attenzione non controlla se è png
|
|
// imgOutputBig.Save(Path.Combine(_DestDir.FullName, "Temp_" & NomeFileBig), thisFormat)
|
|
if (thisFormat.Equals(ImageFormat.Jpeg))
|
|
{
|
|
MakeImageCustomQuality(imgOutputBig, image1Stream, picSettings.JpegQuality);
|
|
}
|
|
//SalvaImmagineCustomQuality(imgOutputBig, Path.Combine(DestDir.FullName, "Temp_" + NomeFileBig), _picSettings.jpegQuality);
|
|
else
|
|
{
|
|
imgOutputBig.Save(image1Stream, thisFormat);
|
|
}
|
|
|
|
//imgOutputBig.Save(Path.Combine(DestDir.FullName, "Temp_" + NomeFileBig), thisFormat);
|
|
image1Stream.Seek(0, SeekOrigin.Begin);
|
|
using var g2 = Image.FromStream(image1Stream);
|
|
imgState.ThumbSizeBig = g2.Width > g2.Height
|
|
? CalculateThumbnailSize(g2.Width, g2.Height, picSettings.LarghezzaBig, "Larghezza")
|
|
: CalculateThumbnailSize(g2.Width, g2.Height, picSettings.AltezzaBig, "Altezza");
|
|
using var imgOutputBig2 = new Bitmap(g2, imgState.ThumbSizeBig.Width, imgState.ThumbSizeBig.Height);
|
|
|
|
if (!picSettings.OverwriteFiles && File.Exists(fileName))
|
|
{
|
|
logger.LogInformation("Saltata foto {FileName}, esiste", fileName);
|
|
}
|
|
else
|
|
{
|
|
if (thisFormat.Equals(ImageFormat.Jpeg))
|
|
SaveImageCustomQuality(imgOutputBig2, fileName, picSettings.JpegQuality);
|
|
else
|
|
imgOutputBig2.Save(fileName, thisFormat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!picSettings.OverwriteFiles && File.Exists(fileName))
|
|
{
|
|
logger.LogInformation("Saltata foto {FileName}, esiste", fileName);
|
|
}
|
|
else
|
|
{
|
|
if (thisFormat.Equals(ImageFormat.Jpeg))
|
|
SaveImageCustomQuality(imgOutputBig, fileName, picSettings.JpegQuality);
|
|
else
|
|
imgOutputBig.Save(fileName, thisFormat);
|
|
}
|
|
}
|
|
|
|
image1Stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
if (!picSettings.CreaMiniature) return;
|
|
if (!picSettings.AggiungiScritteMiniature) return;
|
|
|
|
using var g1 = picSettings.FotoGrandeDimOrigina ? (Image)imgOutputBig.Clone() : Image.FromStream(image1Stream);
|
|
|
|
using var imgOutputSmall = new Bitmap(g1, imgState.ThumbSizeSmall.Width, imgState.ThumbSizeSmall.Height);
|
|
|
|
if (string.Equals(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione,
|
|
StringComparison.OrdinalIgnoreCase))
|
|
imgState.NomeFileSmall = imgState.NomeFileSmall.Substring(0, imgState.NomeFileSmall.Length - 4) +
|
|
picSettings.Codice +
|
|
imgState.NomeFileSmall.Substring(imgState.NomeFileSmall.Length - 4);
|
|
|
|
var tnFileName = Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall);
|
|
|
|
if (!picSettings.OverwriteFiles && File.Exists(tnFileName))
|
|
{
|
|
logger.LogInformation("Saltata miniatura foto {TnFileName}, esiste", tnFileName);
|
|
}
|
|
else
|
|
{
|
|
if (thisFormat.Equals(ImageFormat.Jpeg))
|
|
SaveImageCustomQuality(imgOutputSmall, tnFileName, picSettings.JpegQualityMin);
|
|
else
|
|
imgOutputSmall.Save(tnFileName, thisFormat);
|
|
}
|
|
}
|
|
|
|
private void SaveImageCustomQuality(Bitmap imageToSave, string nomeFileFinale, long quality)
|
|
{
|
|
var jgpEncoder = GetEncoder(ImageFormat.Jpeg);
|
|
var myEncoder = System.Drawing.Imaging.Encoder.Quality;
|
|
|
|
using var myEncoderParameters = new EncoderParameters(1);
|
|
|
|
var myEncoderParameter = new EncoderParameter(myEncoder, quality);
|
|
myEncoderParameters.Param[0] = myEncoderParameter;
|
|
imageToSave.Save(nomeFileFinale, jgpEncoder, myEncoderParameters);
|
|
//imageToSave.Dispose();
|
|
}
|
|
|
|
private void MakeImageCustomQuality(Bitmap imageToSave, Stream destinationStream, long quality)
|
|
{
|
|
var jgpEncoder = GetEncoder(ImageFormat.Jpeg);
|
|
var myEncoder = System.Drawing.Imaging.Encoder.Quality;
|
|
|
|
using var myEncoderParameters = new EncoderParameters(1);
|
|
|
|
var myEncoderParameter = new EncoderParameter(myEncoder, quality);
|
|
myEncoderParameters.Param[0] = myEncoderParameter;
|
|
destinationStream.Seek(0, SeekOrigin.Begin);
|
|
imageToSave.Save(destinationStream, jgpEncoder, myEncoderParameters);
|
|
//imageToSave.Dispose();
|
|
}
|
|
|
|
private ImageCodecInfo GetEncoder(ImageFormat format)
|
|
{
|
|
var codecs = ImageCodecInfo.GetImageDecoders();
|
|
|
|
foreach (var codec in codecs)
|
|
{
|
|
if (codec.FormatID == format.Guid)
|
|
return codec;
|
|
}
|
|
|
|
return null /* TODO Change to default(_) if this is not a reference type */;
|
|
}
|
|
|
|
/// <summary>
|
|
/// ''' Calculate the Size of the New image
|
|
/// ''' </summary>
|
|
/// ''' <param name="currentwidth">Larghezza</param>
|
|
/// ''' <param name="currentheight">Altezza</param>
|
|
/// ''' <param name="maxPixel"></param>
|
|
/// ''' <param name="tipoSize"></param>
|
|
/// ''' <returns></returns>
|
|
/// ''' <remarks></remarks>
|
|
private Size CalculateThumbnailSize(int currentwidth, int currentheight, int maxPixel, string tipoSize)
|
|
{
|
|
// e
|
|
// *** Larghezza, Altezza, Auto
|
|
|
|
double tempMultiplier;
|
|
|
|
if (tipoSize.ToUpper() == "Larghezza".ToUpper())
|
|
tempMultiplier = maxPixel / (double)currentwidth;
|
|
else if (tipoSize.ToUpper() == "Altezza".ToUpper())
|
|
tempMultiplier = maxPixel / (double)currentheight;
|
|
else if (currentheight > currentwidth)
|
|
tempMultiplier = maxPixel / (double)currentheight;
|
|
else
|
|
tempMultiplier = maxPixel / (double)currentwidth;
|
|
|
|
var newSize = new Size(System.Convert.ToInt32(currentwidth * tempMultiplier),
|
|
System.Convert.ToInt32(currentheight * tempMultiplier));
|
|
|
|
return newSize;
|
|
}
|
|
|
|
private void SetVerticalPosition(int imgHeight, float textHeight, ImageState imgState)
|
|
{
|
|
// Use 1% of image height as minimum margin, or 10px, whichever is larger
|
|
float minMargin = Math.Max(10f, imgHeight * 0.01f);
|
|
|
|
switch (picSettings.Posizione.ToUpper())
|
|
{
|
|
case "ALTO":
|
|
imgState.YPosFromBottom1 = Math.Max(minMargin, picSettings.Margine);
|
|
imgState.YPosFromBottom4 = Math.Max(minMargin, picSettings.MargVert);
|
|
break;
|
|
|
|
case "BASSO":
|
|
var bottomMargin1 = (float)(imgHeight * picSettings.Margine / 100.0);
|
|
var bottomMargin4 = (float)(imgHeight * picSettings.MargVert / 100.0);
|
|
|
|
// Position from bottom: bottom edge of text at desired margin from bottom
|
|
// Y = imageHeight - textHeight - bottomMargin
|
|
var desiredY1 = imgHeight - textHeight - bottomMargin1;
|
|
var desiredY4 = imgHeight - textHeight - bottomMargin4;
|
|
|
|
// Ensure text stays completely within bounds:
|
|
// - Top edge must be >= minMargin (not clipped at top)
|
|
// - Bottom edge must be <= imgHeight - minMargin (not clipped at bottom)
|
|
var maxAllowedY1 = imgHeight - textHeight - minMargin; // Maximum Y to keep bottom margin
|
|
var maxAllowedY4 = imgHeight - textHeight - minMargin;
|
|
|
|
imgState.YPosFromBottom1 = Math.Max(minMargin, Math.Min(desiredY1, maxAllowedY1));
|
|
imgState.YPosFromBottom4 = Math.Max(minMargin, Math.Min(desiredY4, maxAllowedY4));
|
|
break;
|
|
|
|
case "CENTRO":
|
|
default:
|
|
// Center the text vertically
|
|
var centeredY = (imgHeight - textHeight) / 2f;
|
|
// Clamp to ensure margins are respected
|
|
imgState.YPosFromBottom1 = Math.Max(minMargin, Math.Min(centeredY, imgHeight - textHeight - minMargin));
|
|
imgState.YPosFromBottom4 = imgState.YPosFromBottom1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private float CalculateHorizontalAlignment(int imgWidth, float textWidth)
|
|
{
|
|
double halfWidth = textWidth / 2.0;
|
|
|
|
return picSettings.Allineamento.ToUpper() switch
|
|
{
|
|
"SINISTRA" => (float)Math.Min(picSettings.Margine + halfWidth, imgWidth / 2.0),
|
|
"DESTRA" => (float)Math.Max(imgWidth - picSettings.Margine - halfWidth, imgWidth / 2.0),
|
|
_ => imgWidth / 2.0f, // CENTRO or default
|
|
};
|
|
}
|
|
|
|
private void DrawText(Graphics g, ImageState imgState, float x, StringFormat format,
|
|
Brush shadowBrush, Brush textBrush, Font font)
|
|
{
|
|
string content = imgState.TestoFirmaPiccola;
|
|
|
|
if (picSettings.TestoMin)
|
|
{
|
|
content = imgState.NomeFileBig;
|
|
}
|
|
else if (picSettings.AggTempoGaraMin && picSettings.UsaTempoGaraTestoApplicare)
|
|
{
|
|
content = FormatTimeText(imgState, includeFileName: false);
|
|
}
|
|
else if (picSettings.AggNumTempMin)
|
|
{
|
|
content = FormatTimeText(imgState, includeFileName: true);
|
|
}
|
|
|
|
var offset = new PointF(x + 1, imgState.YPosFromBottom1 + 1);
|
|
var actual = new PointF(x, imgState.YPosFromBottom1);
|
|
|
|
g.DrawString(content, font, shadowBrush, offset, format);
|
|
g.DrawString(content, font, textBrush, actual, format);
|
|
}
|
|
|
|
private string FormatTimeText(ImageState imgState, bool includeFileName)
|
|
{
|
|
var diff = imgState.DataPartenzaI - imgState.DataFoto;
|
|
var ticks = (long)(diff.TotalSeconds * 10000000);
|
|
var time = new TimeSpan(ticks);
|
|
|
|
var formatted = $"{imgState.TestoOrario}{time:hh\\:mm\\:ss}";
|
|
return includeFileName
|
|
? $"{imgState.NomeFileBig}{Environment.NewLine}{formatted}"
|
|
: Environment.NewLine + formatted;
|
|
}
|
|
}
|