using System; using System.IO; using System.Threading.Tasks; using System.Drawing; using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using System.Linq; using System.Drawing.Imaging; namespace MaddoShared; /// /// Image creator implemented using SixLabors.ImageSharp for core image operations. /// This implementation focuses on loading, EXIF-orientation, resizing and saving. /// It intentionally implements a minimal subset of the original functionality to /// provide a safe and testable replacement. Additional features (text/logo drawing) /// can be added later using ImageSharp.Drawing.Common and SixLabors.Fonts. /// public class ImageCreatorAlternate : IImageCreator { private readonly PicSettings _picSettings; private readonly ILogger _logger; public ImageCreatorAlternate(PicSettings picSettings, ILogger logger) { _picSettings = picSettings ?? throw new ArgumentNullException(nameof(picSettings)); _logger = logger; } public async Task CreateImageAsync(ImageState imgState, System.Drawing.Image logo) { ArgumentNullException.ThrowIfNull(imgState); // Minimal preparation of names and settings normally done by ImageCreatorSharp.PrepareVariables PrepareVariablesMinimal(imgState); try { _logger.LogInformation("[Alternate] Processing {File} -> {Dest}", imgState.WorkFile?.FullName, imgState.DestDir?.FullName); using var fs = File.OpenRead(imgState.WorkFile.FullName); // Load as Rgba32 for general operations using var img = await SixLabors.ImageSharp.Image.LoadAsync(fs).ConfigureAwait(false); // Extract EXIF info (orientation and creation date) ExtractExif(img, imgState); // Apply orientation ApplyExifOrientation(img, imgState); // Determine output format var forceJpg = _picSettings.UsaForzaJpg; // Compute big size var bigSize = ComputeBigSize(img.Width, img.Height); // Resize big image if needed using var imgBig = img.Clone(ctx => ctx.Resize(bigSize.Width, bigSize.Height)); // Ensure destination exists imgState.DestDir?.Create(); var fileNameBig = System.IO.Path.Combine(imgState.DestDir.FullName, imgState.NomeFileBig); // Draw overlays (text/logo) onto big image via GDI+ and save await DrawAndSaveWithGdiAsync(imgBig, fileNameBig, imgState, logo, _picSettings.JpegQuality, isThumbnail: false).ConfigureAwait(false); // Save big image with quality if JPEG var extBig = System.IO.Path.GetExtension(imgState.NomeFileBig)?.ToLowerInvariant() ?? string.Empty; var encoderBig = GetEncoderForExtension(extBig, _picSettings.JpegQuality); await using (var outStream = System.IO.File.Open(fileNameBig, System.IO.FileMode.Create, System.IO.FileAccess.Write)) { await imgBig.SaveAsync(outStream, encoderBig, default).ConfigureAwait(false); } // Create thumbnail if requested if (_picSettings.CreaMiniature) { var smallSize = ComputeSmallSize(img.Width, img.Height); using var imgSmall = img.Clone(ctx => ctx.Resize(smallSize.Width, smallSize.Height)); var fileNameSmall = System.IO.Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall); // Draw overlays and save thumbnail via GDI+ await DrawAndSaveWithGdiAsync(imgSmall, fileNameSmall, imgState, logo, _picSettings.JpegQualityMin, isThumbnail: true).ConfigureAwait(false); } } catch (Exception ex) { _logger.LogError(ex, "[Alternate] Error processing image {File}", imgState.WorkFile?.Name); throw; } } // Thumbnail overlays are rendered by the GDI+ pass in DrawAndSaveWithGdiAsync to match original rendering. private static SixLabors.ImageSharp.Formats.IImageEncoder GetEncoderForExtension(string ext, long quality) { quality = Math.Clamp(quality, 1, 100); return ext switch { ".png" => new SixLabors.ImageSharp.Formats.Png.PngEncoder(), ".gif" => new SixLabors.ImageSharp.Formats.Gif.GifEncoder(), _ => new JpegEncoder { Quality = (int)quality }, }; } private async Task DrawAndSaveWithGdiAsync(Image imgSharp, string outputPath, ImageState imgState, System.Drawing.Image logo, long quality, bool isThumbnail) { // Convert ImageSharp image to System.Drawing.Bitmap via MemoryStream PNG to preserve alpha await using var ms = new MemoryStream(); await imgSharp.SaveAsPngAsync(ms).ConfigureAwait(false); ms.Seek(0, SeekOrigin.Begin); using var bmp = new Bitmap(ms); using var g = Graphics.FromImage(bmp); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; // Prepare text var text = isThumbnail ? imgState.TestoFirmaPiccola : imgState.TestoFirma; if (string.IsNullOrEmpty(text) && _picSettings.TestoNome) text = imgState.NomeFileBig; if (!string.IsNullOrEmpty(text)) { var fontSize = isThumbnail ? imgState.DimensioneStandardMiniatura : imgState.DimensioneStandard; using var font = new System.Drawing.Font(_picSettings.IlFont ?? "Arial", Math.Max(6, fontSize)); using var shadowBrush = new SolidBrush(System.Drawing.Color.FromArgb(imgState.AlphaScelta, 0, 0, 0)); using var textBrush = new SolidBrush(System.Drawing.Color.FromArgb(imgState.AlphaScelta, _picSettings.FontColoreRGB)); var sf = new StringFormat { Alignment = StringAlignment.Center }; var x = bmp.Width / 2f; var y = isThumbnail ? bmp.Height - fontSize - 4 : bmp.Height - fontSize - (_picSettings.Margine); g.DrawString(text, font, shadowBrush, new System.Drawing.PointF(x + 1, y + 1), sf); g.DrawString(text, font, textBrush, new System.Drawing.PointF(x, y), sf); } // Draw logo if provided if (logo != null && _picSettings.LogoAggiungi && File.Exists(_picSettings.LogoNomeFile)) { try { var target = new System.Drawing.Size(_picSettings.LogoLarghezza, _picSettings.LogoAltezza); using var logoResized = new Bitmap(logo, target.Width, target.Height); int xPos = _picSettings.LogoPosizioneH?.ToUpperInvariant() == "DESTRA" ? bmp.Width - target.Width - _picSettings.Margine : _picSettings.Margine; int yPos = _picSettings.LogoPosizioneV?.ToUpperInvariant() == "BASSO" ? bmp.Height - target.Height - _picSettings.Margine : _picSettings.Margine; var cm = new System.Drawing.Imaging.ColorMatrix { Matrix33 = (float)Math.Clamp((int.TryParse(_picSettings.LogoTrasparenza, out var lt) ? lt : 100) / 100.0, 0.0, 1.0) }; var ia = new System.Drawing.Imaging.ImageAttributes(); ia.SetColorMatrix(cm, System.Drawing.Imaging.ColorMatrixFlag.Default, System.Drawing.Imaging.ColorAdjustType.Bitmap); g.DrawImage(logoResized, new System.Drawing.Rectangle(xPos, yPos, target.Width, target.Height), 0, 0, target.Width, target.Height, GraphicsUnit.Pixel, ia); } catch (Exception ex) { _logger.LogError(ex, "[Alternate] Error drawing logo in GDI pass"); } } // Ensure directory var dir = System.IO.Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); // Save with requested quality using GDI encoder var encoder = GetEncoder(ImageFormat.Jpeg); var myEncoder = System.Drawing.Imaging.Encoder.Quality; using var encoderParams = new System.Drawing.Imaging.EncoderParameters(1); encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(myEncoder, quality); bmp.Save(outputPath, encoder, encoderParams); } private static ImageCodecInfo GetEncoder(System.Drawing.Imaging.ImageFormat format) { var codecs = ImageCodecInfo.GetImageDecoders(); foreach (var codec in codecs) { if (codec.FormatID == format.Guid) return codec; } return null; } private void PrepareVariablesMinimal(ImageState imgState) { imgState.NomeFileBig = imgState.WorkFile.Name; imgState.NomeFileSmall = (_picSettings.Suffisso ?? string.Empty) + imgState.WorkFile.Name; imgState.DimensioneStandard = _picSettings.DimStandard; imgState.DimensioneStandardMiniatura = _picSettings.DimStandardMiniatura; // sanitize imgState.NomeFileBig = SanitizeFileName(imgState.NomeFileBig); imgState.NomeFileSmall = SanitizeFileName(imgState.NomeFileSmall); } private static string SanitizeFileName(string fileName) { if (string.IsNullOrEmpty(fileName)) return fileName; var invalid = System.IO.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 ExtractExif(Image img, ImageState imgState) { imgState.Orientation = Orientations.TopLeft; imgState.CreationDate = null; var profile = img.Metadata?.ExifProfile; if (profile is null) return; IExifValue rotation = null; var found = profile.TryGetValue(ExifTag.Orientation, out rotation); if (found && rotation != null) { imgState.Orientation = (Orientations)Convert.ToInt32(rotation.Value); } IExifValue date = null; var creationFound = profile.TryGetValue(ExifTag.DateTimeOriginal, out date); if (creationFound && date != null) { if (DateTime.TryParseExact(date.Value, "yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var cr)) { imgState.CreationDate = cr; } else { imgState.CreationDate = null; } } else { imgState.CreationDate = null; } } private void ApplyExifOrientation(Image img, ImageState imgState) { // Common EXIF orientations: 1=TopLeft, 3=BottomRight (rotate 180), 6=RightTop (rotate 90 CW), 8=LeftBottom (rotate 270 CW) switch (imgState.Orientation) { case Orientations.RightTop: // 6 img.Mutate(x => x.Rotate(RotateMode.Rotate90)); break; case Orientations.BottomRight: // 3 img.Mutate(x => x.Rotate(RotateMode.Rotate180)); break; case Orientations.LftBottom: // 8 img.Mutate(x => x.Rotate(RotateMode.Rotate270)); break; default: break; } } private System.Drawing.Size ComputeBigSize(int width, int height) { // If original large size option requested, return original // otherwise compute based on width/height limits from settings return width > height ? CalculateThumbnailSize(width, height, _picSettings.LarghezzaBig, "Larghezza") : CalculateThumbnailSize(width, height, _picSettings.AltezzaBig, "Altezza"); } private System.Drawing.Size ComputeSmallSize(int width, int height) { return width > height ? CalculateThumbnailSize(width, height, _picSettings.LarghezzaSmall, "Larghezza") : CalculateThumbnailSize(width, height, _picSettings.AltezzaSmall, "Altezza"); } // Helper to access PicSettings values via instance _picSettings private static System.Drawing.Size CalculateThumbnailSize(int currentwidth, int currentheight, int maxPixel, string tipoSize) { double tempMultiplier; if (string.Equals(tipoSize, "Larghezza", StringComparison.OrdinalIgnoreCase)) tempMultiplier = maxPixel / (double)currentwidth; else if (string.Equals(tipoSize, "Altezza", StringComparison.OrdinalIgnoreCase)) tempMultiplier = maxPixel / (double)currentheight; else if (currentheight > currentwidth) tempMultiplier = maxPixel / (double)currentheight; else tempMultiplier = maxPixel / (double)currentwidth; var newSize = new System.Drawing.Size(Convert.ToInt32(currentwidth * tempMultiplier), Convert.ToInt32(currentheight * tempMultiplier)); return newSize; } }