From 8872080741e43cd2192125b4913957e46cce984b Mon Sep 17 00:00:00 2001 From: MaddoScientisto Date: Sun, 15 Feb 2026 11:13:23 +0100 Subject: [PATCH] Add color-key transparency support for logo overlays - Allow users to select a transparent color for logo images (watermarks) via UI (checkbox, color picker, preview). - Apply color-key transparency in both System.Drawing and ImageSharp backends: specified color in logo is made fully transparent. - Persist transparency settings in PicSettings and SettingsDto; bind to DataModel and UI controls. - Update logo preview to reflect transparency in real time. - Add option to select image processing library (System.Drawing or ImageSharp) in UI and settings. - Fix bug in SettingsService parameter loading for int/double/DateTime. - Fully integrate color-key transparency into image processing and settings serialization. --- MaddoShared/ImageCreatorAlternate.cs | 355 +++++++++++++++++++---- MaddoShared/ImageCreatorSharp.cs | 37 ++- MaddoShared/PicSettings.cs | 16 + imagecatalog/DataModel.cs | 30 ++ imagecatalog/MainForm.Designer.cs | 169 ++++++----- imagecatalog/MainForm.cs | 264 ++++++++++++++++- imagecatalog/MainForm.resx | 3 + imagecatalog/Models/SettingsDto.cs | 14 + imagecatalog/Services/SettingsService.cs | 6 +- 9 files changed, 729 insertions(+), 165 deletions(-) diff --git a/MaddoShared/ImageCreatorAlternate.cs b/MaddoShared/ImageCreatorAlternate.cs index 9ff74ec..8ddfbec 100644 --- a/MaddoShared/ImageCreatorAlternate.cs +++ b/MaddoShared/ImageCreatorAlternate.cs @@ -1,7 +1,7 @@ using System; using System.IO; using System.Threading.Tasks; -using System.Drawing; +// System.Drawing not required for ImageSharp-based drawing in this class using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; @@ -10,7 +10,9 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using System.Linq; -using System.Drawing.Imaging; +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing; namespace MaddoShared; @@ -68,17 +70,9 @@ public class ImageCreatorAlternate : IImageCreator var fileNameBig = System.IO.Path.Combine(imgState.DestDir.FullName, imgState.NomeFileBig); - // Draw overlays (text/logo) onto big image via GDI+ and save + // Draw overlays (text/logo) onto big image using ImageSharp 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) { @@ -87,7 +81,7 @@ public class ImageCreatorAlternate : IImageCreator var fileNameSmall = System.IO.Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall); - // Draw overlays and save thumbnail via GDI+ + // Draw overlays and save thumbnail via ImageSharp await DrawAndSaveWithGdiAsync(imgSmall, fileNameSmall, imgState, logo, _picSettings.JpegQualityMin, isThumbnail: true).ConfigureAwait(false); } } @@ -115,14 +109,28 @@ public class ImageCreatorAlternate : IImageCreator 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); + // Use ImageSharp drawing APIs to render text and logos and save using ImageSharp encoders. + // Clone editable image so we don't mutate the original reference unexpectedly. + using var working = imgSharp.Clone(); - using var bmp = new Bitmap(ms); - using var g = Graphics.FromImage(bmp); - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + // Ensure DataFoto is set (extracted earlier) so time-based text is available + imgState.DataFoto = imgState.CreationDate ?? DateTime.Now; + + // Ensure thumbnail text is prepared similarly to ImageCreatorSharp logic + if (isThumbnail) + { + if (string.IsNullOrEmpty(imgState.TestoFirmaPiccola)) + { + if (_picSettings.TestoMin) + imgState.TestoFirmaPiccola = imgState.NomeFileBig; + else if (_picSettings.AggNumTempMin) + imgState.TestoFirmaPiccola = imgState.NomeFileBig + " "; + else if (_picSettings.UsaOrarioMiniatura) + imgState.TestoFirmaPiccola = imgState.DataFoto.ToShortTimeString(); + else + imgState.TestoFirmaPiccola ??= string.Empty; + } + } // Prepare text var text = isThumbnail ? imgState.TestoFirmaPiccola : imgState.TestoFirma; @@ -131,39 +139,251 @@ public class ImageCreatorAlternate : IImageCreator 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)); + // Choose target starting font size + var baseSize = isThumbnail ? imgState.DimensioneStandardMiniatura : imgState.DimensioneStandard; - var sf = new StringFormat { Alignment = StringAlignment.Center }; - var x = bmp.Width / 2f; - var y = isThumbnail ? bmp.Height - fontSize - 4 : bmp.Height - fontSize - (_picSettings.Margine); + // Create font with fallback + SixLabors.Fonts.Font font; + try + { + var fontName = _picSettings.IlFont ?? SixLabors.Fonts.SystemFonts.Collection.Families.First().Name; + font = SixLabors.Fonts.SystemFonts.CreateFont(fontName, Math.Max(6, baseSize)); + } + catch + { + font = SixLabors.Fonts.SystemFonts.CreateFont(SixLabors.Fonts.SystemFonts.Collection.Families.First().Name, Math.Max(6, baseSize)); + } - 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); + // Color with alpha + var textColor = SixLabors.ImageSharp.Color.Black; + try + { + var c = _picSettings.FontColoreRGB; + textColor = SixLabors.ImageSharp.Color.FromRgba(c.R, c.G, c.B, (byte)imgState.AlphaScelta); + } + catch + { + textColor = SixLabors.ImageSharp.Color.Black; + } + var shadowColor = SixLabors.ImageSharp.Color.FromRgba(0, 0, 0, (byte)imgState.AlphaScelta); + + // Find best font size so text fits image width (allow 95% width) and not exceed ~15% height + var maxTextWidth = working.Width * 0.95f; + var maxTextHeight = working.Height * 0.15f; + + var chosenSize = FindBestFontSize(text, font.Name, Math.Max(6, baseSize), maxTextWidth, maxTextHeight); + + // Use final font + var finalFont = SixLabors.Fonts.SystemFonts.CreateFont(font.Name, chosenSize); + if (!isThumbnail) imgState.DimensioneStandard = (int)Math.Round(chosenSize); + + // Approximate measured size since TextMeasurer/RendererOptions may not be available in this Fonts version + var approxWidth = finalFont.Size * text.Length * 0.6f; + var approxHeight = finalFont.Size * 1.0f; + + // Compute horizontal position based on alignment + float xCenterOfImg; + switch ((_picSettings.Allineamento ?? string.Empty).ToUpperInvariant()) + { + case "SINISTRA": + xCenterOfImg = Math.Min(_picSettings.Margine + (approxWidth / 2f), working.Width / 2f); + break; + case "DESTRA": + xCenterOfImg = Math.Max(working.Width - _picSettings.Margine - (approxWidth / 2f), working.Width / 2f); + break; + default: + xCenterOfImg = working.Width / 2f; + break; + } + + // Compute vertical position similar to ImageCreatorSharp behaviour + float originY; + var pos = (_picSettings.Posizione ?? string.Empty).ToUpperInvariant(); + if (pos == "ALTO") + { + originY = _picSettings.Margine; + } + else if (pos == "BASSO") + { + originY = (float)(working.Height - approxHeight - (working.Height * _picSettings.Margine / 100.0)); + } + else + { + originY = (working.Height - approxHeight) / 2f; + } + + // Compute origin X so the text is centered around xCenterOfImg + var originX = xCenterOfImg - approxWidth / 2f; + + // Clamp origin so text remains inside the image bounds + if (approxWidth > working.Width) + { + approxWidth = working.Width * 0.95f; + } + if (approxHeight > working.Height) + { + approxHeight = working.Height * 0.9f; + } + + originX = Math.Max(0, Math.Min(originX, working.Width - approxWidth)); + originY = Math.Max(0, Math.Min(originY, working.Height - approxHeight)); + + // Draw shadow then text + working.Mutate(ctx => + { + ctx.DrawText(text, finalFont, shadowColor, new SixLabors.ImageSharp.PointF(originX + 1, originY + 1)); + ctx.DrawText(text, finalFont, textColor, new SixLabors.ImageSharp.PointF(originX, originY)); + }); } // Draw logo if provided - if (logo != null && _picSettings.LogoAggiungi && File.Exists(_picSettings.LogoNomeFile)) + if (logo != null && _picSettings.LogoAggiungi) { try { - var target = new System.Drawing.Size(_picSettings.LogoLarghezza, _picSettings.LogoAltezza); - using var logoResized = new Bitmap(logo, target.Width, target.Height); + Image logoImg = null; - 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; + // Prefer configured file if present, otherwise use the provided System.Drawing.Image instance + if (!string.IsNullOrEmpty(_picSettings.LogoNomeFile) && File.Exists(_picSettings.LogoNomeFile)) + { + using var logoStream = File.OpenRead(_picSettings.LogoNomeFile); + logoImg = await SixLabors.ImageSharp.Image.LoadAsync(logoStream).ConfigureAwait(false); + } + else + { + // Convert System.Drawing.Image to ImageSharp by saving to PNG in-memory to preserve alpha + await using var ms = new MemoryStream(); + logo.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + ms.Seek(0, SeekOrigin.Begin); + logoImg = await SixLabors.ImageSharp.Image.LoadAsync(ms).ConfigureAwait(false); + } - 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); + var fotoLogoH = _picSettings.LogoAltezza; + var fotoLogoW = _picSettings.LogoLarghezza; - g.DrawImage(logoResized, new System.Drawing.Rectangle(xPos, yPos, target.Width, target.Height), 0, 0, target.Width, target.Height, GraphicsUnit.Pixel, ia); + // When rendering on thumbnails, limit logo size relative to the thumbnail dimensions + var effectiveMaxLogoWidth = fotoLogoW; + var effectiveMaxLogoHeight = fotoLogoH; + if (isThumbnail) + { + // Do not allow the logo to occupy more than ~30% of the thumbnail area + effectiveMaxLogoWidth = Math.Max(1, Math.Min(fotoLogoW, (int)(working.Width * 0.30))); + effectiveMaxLogoHeight = Math.Max(1, Math.Min(fotoLogoH, (int)(working.Height * 0.30))); + } + + // Compute new logo size preserving aspect ratio, but avoid upscaling the logo beyond its native size + var targetByWidth = CalculateThumbnailSize(logoImg.Width, logoImg.Height, effectiveMaxLogoWidth, "Larghezza"); + var targetByHeight = CalculateThumbnailSize(logoImg.Width, logoImg.Height, effectiveMaxLogoHeight, "Altezza"); + var nuovaSize = (targetByWidth.Width <= targetByHeight.Width) ? targetByWidth : targetByHeight; + + // Prevent upscaling: clamp to original logo size + if (nuovaSize.Width > logoImg.Width) nuovaSize.Width = logoImg.Width; + if (nuovaSize.Height > logoImg.Height) nuovaSize.Height = logoImg.Height; + + // Parse logo margin (may be percentage string like "10%") + var logoMargineStr = _picSettings.LogoMargine ?? string.Empty; + var inPercentualeL = logoMargineStr.Trim().EndsWith('%'); + var margineL = 0; + if (inPercentualeL) + { + var trimmed = logoMargineStr.Trim().TrimEnd('%'); + if (!int.TryParse(trimmed, out margineL)) margineL = 0; + } + else + { + if (!int.TryParse(logoMargineStr, out margineL)) margineL = 0; + } + var margineUsato = inPercentualeL ? Convert.ToInt32(working.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 = Convert.ToInt32((working.Width - nuovaSize.Width) / (double)2); + break; + case "DESTRA": + xPosOfWm = ((working.Width - nuovaSize.Width) - margineUsato); + break; + } + + switch (logoV) + { + case "ALTO": + case "NESSUNA": + yPosOfWm = margineUsato; + break; + case "CENTRO": + yPosOfWm = Convert.ToInt32((working.Height - nuovaSize.Height) / (double)2); + break; + case "BASSO": + yPosOfWm = ((working.Height - nuovaSize.Height) - margineUsato); + break; + } + + var transparency = 1.0f; + if (int.TryParse(_picSettings.LogoTrasparenza, out var lt)) transparency = Math.Clamp(lt / 100f, 0f, 1f); + + // Resize logo to nuovaSize + logoImg.Mutate(x => x.Resize(nuovaSize.Width, nuovaSize.Height)); + + // If configured to use color-key transparency for non-PNG logos, replace the key color with transparent + if (_picSettings.UseTransparentColor && !string.IsNullOrEmpty(_picSettings.TransparentColor)) + { + try + { + var hex = _picSettings.TransparentColor.Trim(); + // Allow either #RRGGBB or RRGGBB + if (hex.StartsWith("#")) hex = hex.Substring(1); + if (hex.Length == 6) + { + var r = Convert.ToByte(hex.Substring(0, 2), 16); + var g = Convert.ToByte(hex.Substring(2, 2), 16); + var b = Convert.ToByte(hex.Substring(4, 2), 16); + var key = SixLabors.ImageSharp.Color.FromRgb(r, g, b); + + // Replace matching pixels (exact match) with transparent + logoImg.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + var row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + var p = row[x]; + if (p.R == r && p.G == g && p.B == b) + { + row[x] = new Rgba32(p.R, p.G, p.B, 0); + } + } + } + }); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "[Alternate] Invalid transparent color setting {Color}", _picSettings.TransparentColor); + } + } + + // Ensure logo position keeps it inside the canvas (avoid clipping) + xPosOfWm = Math.Max(0, Math.Min(xPosOfWm, working.Width - nuovaSize.Width)); + yPosOfWm = Math.Max(0, Math.Min(yPosOfWm, working.Height - nuovaSize.Height)); + + // Draw logo with opacity + working.Mutate(ctx => ctx.DrawImage(logoImg, new SixLabors.ImageSharp.Point(xPosOfWm, yPosOfWm), (float)transparency)); + + logoImg.Dispose(); } catch (Exception ex) { - _logger.LogError(ex, "[Alternate] Error drawing logo in GDI pass"); + _logger.LogError(ex, "[Alternate] Error drawing logo in ImageSharp pass"); } } @@ -171,23 +391,14 @@ public class ImageCreatorAlternate : IImageCreator 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); + // Save with requested quality using ImageSharp encoder + var ext = System.IO.Path.GetExtension(outputPath)?.ToLowerInvariant() ?? string.Empty; + var encoder = GetEncoderForExtension(ext, quality); + await using var outStream = System.IO.File.Open(outputPath, System.IO.FileMode.Create, System.IO.FileAccess.Write); + await working.SaveAsync(outStream, encoder).ConfigureAwait(false); } - 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; - } + // Removed GDI encoder helper; ImageSharp encoders are used instead. private void PrepareVariablesMinimal(ImageState imgState) { @@ -196,6 +407,16 @@ public class ImageCreatorAlternate : IImageCreator imgState.DimensioneStandard = _picSettings.DimStandard; imgState.DimensioneStandardMiniatura = _picSettings.DimStandardMiniatura; + // basic text / transparency defaults used by drawing routines + // AlphaScelta mirrors ImageCreatorSharp behavior: compute from PicSettings.Trasparenza (0-100) + imgState.AlphaScelta = Convert.ToInt32((255 * (100 - _picSettings.Trasparenza) / (double)100)); + + // Set minimal text fields so text drawing has fallback values + imgState.TestoFirma ??= _picSettings.TestoFirmaStart ?? string.Empty; + imgState.TestoFirmaPiccola ??= string.Empty; + imgState.DataPartenzaI = _picSettings.DataPartenza; + imgState.TestoOrario = _picSettings.TestoOrario ?? string.Empty; + // sanitize imgState.NomeFileBig = SanitizeFileName(imgState.NomeFileBig); imgState.NomeFileSmall = SanitizeFileName(imgState.NomeFileSmall); @@ -297,5 +518,33 @@ public class ImageCreatorAlternate : IImageCreator var newSize = new System.Drawing.Size(Convert.ToInt32(currentwidth * tempMultiplier), Convert.ToInt32(currentheight * tempMultiplier)); return newSize; } + + private static float FindBestFontSize(string text, string fontName, int maxSize, float maxWidth, float maxHeight, int minSize = 6) + { + 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; + // Approximate measurement: width ~ size * chars * 0.6, height ~ size + var approxWidth = mid * text.Length * 0.6f; + var approxHeight = mid * 1.0f; + if (approxWidth <= maxWidth && approxHeight <= maxHeight) + { + best = mid; + low = mid + 1; // try larger + } + else + { + high = mid - 1; // too big + } + } + + return best; + } } diff --git a/MaddoShared/ImageCreatorSharp.cs b/MaddoShared/ImageCreatorSharp.cs index cfd54e2..e7fc59a 100644 --- a/MaddoShared/ImageCreatorSharp.cs +++ b/MaddoShared/ImageCreatorSharp.cs @@ -486,22 +486,37 @@ public class ImageCreatorSharp(PicSettings picSettings, ILogger TransparentColorHex; + set => TransparentColorHex = value; + } + + public bool UseTransparentColor + { + get => UseTransparentColorKey; + set => UseTransparentColorKey = value; + } public bool FotoGrandeDimOrigina { get; set; } public int AltezzaBig { get; set; } public int LarghezzaBig { get; set; } diff --git a/imagecatalog/DataModel.cs b/imagecatalog/DataModel.cs index d59cb07..6d90981 100644 --- a/imagecatalog/DataModel.cs +++ b/imagecatalog/DataModel.cs @@ -30,6 +30,7 @@ namespace ImageCatalog_2 public ICommand SaveSettingsCommand { get; } public ICommand LoadSettingsCommand { get; } public ICommand SelectColorCommand { get; } + public ICommand SelectTransparentColorCommand { get; } private readonly ITestService _service; private readonly ILogger _logger; @@ -67,6 +68,7 @@ namespace ImageCatalog_2 SaveSettingsCommand = new RelayCommand(SaveSettings); LoadSettingsCommand = new RelayCommand(LoadSettings); SelectColorCommand = new RelayCommand(SelectColor); + SelectTransparentColorCommand = new RelayCommand(SelectTransparentColor); // Load available fonts AvailableFonts = LoadAvailableFonts(); @@ -341,6 +343,28 @@ namespace ImageCatalog_2 } } + private string _transparentColor = "#FFFFFF"; + public string TransparentColor + { + get => _transparentColor; + set + { + _transparentColor = value; + NotifyPropertyChanged(); + } + } + + private bool _useTransparentColor; + public bool UseTransparentColor + { + get => _useTransparentColor; + set + { + _useTransparentColor = value; + NotifyPropertyChanged(); + } + } + // Logo/Watermark settings private string _logoFile = ""; public string LogoFile @@ -1129,6 +1153,7 @@ namespace ImageCatalog_2 public event EventHandler SelectColorRequested; // Request that the View shows a message to the user (message, caption, icon) public event EventHandler> ShowMessageRequested; + public event EventHandler SelectTransparentColorRequested; private void SelectSourceFolder(object parameter) { @@ -1160,6 +1185,11 @@ namespace ImageCatalog_2 SelectColorRequested?.Invoke(this, EventArgs.Empty); } + private void SelectTransparentColor(object parameter) + { + SelectTransparentColorRequested?.Invoke(this, EventArgs.Empty); + } + public async Task SaveSettingsToFileAsync(string filePath) { await _settingsService.SaveSettingsAsync(filePath, this); diff --git a/imagecatalog/MainForm.Designer.cs b/imagecatalog/MainForm.Designer.cs index 5865046..edb3bb1 100644 --- a/imagecatalog/MainForm.Designer.cs +++ b/imagecatalog/MainForm.Designer.cs @@ -45,6 +45,9 @@ namespace ImageCatalog Label43 = new Label(); TabControl1 = new TabControl(); TabPage5 = new TabPage(); + groupBox12 = new GroupBox(); + rdbLibrary2 = new RadioButton(); + rdbLibrary1 = new RadioButton(); GroupBox11 = new GroupBox(); numericUpDown2 = new NumericUpDown(); numericUpDown1 = new NumericUpDown(); @@ -150,7 +153,6 @@ namespace ImageCatalog CheckBox1 = new CheckBox(); TabPage4 = new TabPage(); GroupBox6 = new GroupBox(); - PictureBox2 = new PictureBox(); _PictureBox1 = new PictureBox(); ComboBox5 = new ComboBox(); ComboBox4 = new ComboBox(); @@ -167,8 +169,6 @@ namespace ImageCatalog _Button4 = new Button(); TextBox10 = new TextBox(); Label29 = new Label(); - Label30 = new Label(); - PictureBox3 = new PictureBox(); versionLabel = new Label(); _Button7 = new Button(); _Button5 = new Button(); @@ -181,13 +181,15 @@ namespace ImageCatalog _btnCreaCatalogoAsync = new Button(); timer1 = new System.Windows.Forms.Timer(components); dataModelBindingSource1 = new BindingSource(components); - groupBox12 = new GroupBox(); - rdbLibrary1 = new RadioButton(); - rdbLibrary2 = new RadioButton(); + PictureBox3 = new PictureBox(); + colorDialog1 = new ColorDialog(); + btnSetTransparency = new Button(); + chkUseTransparentColor = new CheckBox(); ((System.ComponentModel.ISupportInitialize)bindingSource1).BeginInit(); ((System.ComponentModel.ISupportInitialize)dataModelBindingSource).BeginInit(); TabControl1.SuspendLayout(); TabPage5.SuspendLayout(); + groupBox12.SuspendLayout(); GroupBox11.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)numericUpDown2).BeginInit(); ((System.ComponentModel.ISupportInitialize)numericUpDown1).BeginInit(); @@ -208,11 +210,9 @@ namespace ImageCatalog Panel2.SuspendLayout(); TabPage4.SuspendLayout(); GroupBox6.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)PictureBox2).BeginInit(); ((System.ComponentModel.ISupportInitialize)_PictureBox1).BeginInit(); - ((System.ComponentModel.ISupportInitialize)PictureBox3).BeginInit(); ((System.ComponentModel.ISupportInitialize)dataModelBindingSource1).BeginInit(); - groupBox12.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)PictureBox3).BeginInit(); SuspendLayout(); // // ProgressBar1 @@ -286,6 +286,41 @@ namespace ImageCatalog TabPage5.Text = "Generale"; TabPage5.UseVisualStyleBackColor = true; // + // groupBox12 + // + groupBox12.Controls.Add(rdbLibrary2); + groupBox12.Controls.Add(rdbLibrary1); + groupBox12.Location = new Point(405, 625); + groupBox12.Name = "groupBox12"; + groupBox12.Size = new Size(350, 175); + groupBox12.TabIndex = 49; + groupBox12.TabStop = false; + groupBox12.Text = "Libreria Manipolazione Grafica"; + // + // rdbLibrary2 + // + rdbLibrary2.AutoSize = true; + rdbLibrary2.DataBindings.Add(new Binding("Checked", bindingSource1, "UseImageSharp", true, DataSourceUpdateMode.OnPropertyChanged)); + rdbLibrary2.Location = new Point(12, 77); + rdbLibrary2.Name = "rdbLibrary2"; + rdbLibrary2.Size = new Size(149, 34); + rdbLibrary2.TabIndex = 1; + rdbLibrary2.TabStop = true; + rdbLibrary2.Text = "ImageSharp"; + rdbLibrary2.UseVisualStyleBackColor = true; + // + // rdbLibrary1 + // + rdbLibrary1.AutoSize = true; + rdbLibrary1.DataBindings.Add(new Binding("Checked", bindingSource1, "UseSystemGraphics", true, DataSourceUpdateMode.OnPropertyChanged)); + rdbLibrary1.Location = new Point(12, 37); + rdbLibrary1.Name = "rdbLibrary1"; + rdbLibrary1.Size = new Size(188, 34); + rdbLibrary1.TabIndex = 0; + rdbLibrary1.TabStop = true; + rdbLibrary1.Text = "System.Graphics"; + rdbLibrary1.UseVisualStyleBackColor = true; + // // GroupBox11 // GroupBox11.Controls.Add(numericUpDown2); @@ -1491,7 +1526,8 @@ namespace ImageCatalog // // GroupBox6 // - GroupBox6.Controls.Add(PictureBox2); + GroupBox6.Controls.Add(chkUseTransparentColor); + GroupBox6.Controls.Add(btnSetTransparency); GroupBox6.Controls.Add(_PictureBox1); GroupBox6.Controls.Add(ComboBox5); GroupBox6.Controls.Add(ComboBox4); @@ -1508,29 +1544,17 @@ namespace ImageCatalog GroupBox6.Controls.Add(_Button4); GroupBox6.Controls.Add(TextBox10); GroupBox6.Controls.Add(Label29); - GroupBox6.Controls.Add(Label30); GroupBox6.Controls.Add(PictureBox3); GroupBox6.ForeColor = Color.FromArgb(0, 0, 192); GroupBox6.Location = new Point(12, 13); GroupBox6.Margin = new Padding(6, 8, 6, 8); GroupBox6.Name = "GroupBox6"; GroupBox6.Padding = new Padding(6, 8, 6, 8); - GroupBox6.Size = new Size(991, 518); + GroupBox6.Size = new Size(991, 593); GroupBox6.TabIndex = 42; GroupBox6.TabStop = false; GroupBox6.Text = "Logo"; // - // PictureBox2 - // - PictureBox2.BorderStyle = BorderStyle.FixedSingle; - PictureBox2.Location = new Point(288, 442); - PictureBox2.Margin = new Padding(6, 8, 6, 8); - PictureBox2.Name = "PictureBox2"; - PictureBox2.Size = new Size(45, 53); - PictureBox2.TabIndex = 44; - PictureBox2.TabStop = false; - PictureBox2.Visible = false; - // // _PictureBox1 // _PictureBox1.Cursor = Cursors.Cross; @@ -1575,7 +1599,7 @@ namespace ImageCatalog // Label28 // Label28.ForeColor = Color.Black; - Label28.Location = new Point(34, 223); + Label28.Location = new Point(38, 222); Label28.Margin = new Padding(6, 0, 6, 0); Label28.Name = "Label28"; Label28.Size = new Size(240, 38); @@ -1587,7 +1611,7 @@ namespace ImageCatalog // CheckBox5.DataBindings.Add(new Binding("Checked", bindingSource1, "AddLogo", true, DataSourceUpdateMode.OnPropertyChanged)); CheckBox5.ForeColor = Color.Black; - CheckBox5.Location = new Point(0, 60); + CheckBox5.Location = new Point(12, 56); CheckBox5.Margin = new Padding(6, 8, 6, 8); CheckBox5.Name = "CheckBox5"; CheckBox5.Size = new Size(144, 47); @@ -1618,7 +1642,7 @@ namespace ImageCatalog // Label25.AutoSize = true; Label25.ForeColor = Color.Black; - Label25.Location = new Point(34, 387); + Label25.Location = new Point(39, 390); Label25.Margin = new Padding(6, 0, 6, 0); Label25.Name = "Label25"; Label25.Size = new Size(183, 30); @@ -1695,7 +1719,7 @@ namespace ImageCatalog // Label29.AutoSize = true; Label29.ForeColor = Color.Black; - Label29.Location = new Point(34, 339); + Label29.Location = new Point(38, 341); Label29.Margin = new Padding(6, 0, 6, 0); Label29.Name = "Label29"; Label29.Size = new Size(208, 30); @@ -1703,30 +1727,6 @@ namespace ImageCatalog Label29.Text = "Posizione orizzontale"; Label29.TextAlign = ContentAlignment.MiddleLeft; // - // Label30 - // - Label30.AutoSize = true; - Label30.ForeColor = Color.Black; - Label30.Location = new Point(38, 468); - Label30.Margin = new Padding(6, 0, 6, 0); - Label30.Name = "Label30"; - Label30.Size = new Size(184, 30); - Label30.TabIndex = 36; - Label30.Text = "Colore trasparente"; - Label30.TextAlign = ContentAlignment.MiddleLeft; - Label30.Visible = false; - // - // PictureBox3 - // - PictureBox3.BorderStyle = BorderStyle.FixedSingle; - PictureBox3.Location = new Point(432, 442); - PictureBox3.Margin = new Padding(6, 8, 6, 8); - PictureBox3.Name = "PictureBox3"; - PictureBox3.Size = new Size(45, 53); - PictureBox3.TabIndex = 44; - PictureBox3.TabStop = false; - PictureBox3.Visible = false; - // // versionLabel // versionLabel.DataBindings.Add(new Binding("Text", bindingSource1, "AppVersion", true)); @@ -1842,40 +1842,35 @@ namespace ImageCatalog // dataModelBindingSource1.DataSource = typeof(ImageCatalog_2.DataModel); // - // groupBox12 + // PictureBox3 // - groupBox12.Controls.Add(rdbLibrary2); - groupBox12.Controls.Add(rdbLibrary1); - groupBox12.Location = new Point(405, 625); - groupBox12.Name = "groupBox12"; - groupBox12.Size = new Size(350, 175); - groupBox12.TabIndex = 49; - groupBox12.TabStop = false; - groupBox12.Text = "Libreria Manipolazione Grafica"; + PictureBox3.BorderStyle = BorderStyle.FixedSingle; + PictureBox3.Location = new Point(432, 442); + PictureBox3.Margin = new Padding(6, 8, 6, 8); + PictureBox3.Name = "PictureBox3"; + PictureBox3.Size = new Size(45, 53); + PictureBox3.TabIndex = 44; + PictureBox3.TabStop = false; + PictureBox3.Visible = false; // - // rdbLibrary1 + // btnSetTransparency // - rdbLibrary1.AutoSize = true; - rdbLibrary1.Location = new Point(12, 37); - rdbLibrary1.Name = "rdbLibrary1"; - rdbLibrary1.Size = new Size(188, 34); - rdbLibrary1.TabIndex = 0; - rdbLibrary1.TabStop = true; - rdbLibrary1.Text = "System.Graphics"; - rdbLibrary1.DataBindings.Add(new Binding("Checked", bindingSource1, "UseSystemGraphics", true, DataSourceUpdateMode.OnPropertyChanged)); - rdbLibrary1.UseVisualStyleBackColor = true; + btnSetTransparency.Location = new Point(288, 455); + btnSetTransparency.Name = "btnSetTransparency"; + btnSetTransparency.Size = new Size(131, 40); + btnSetTransparency.TabIndex = 45; + btnSetTransparency.Text = "Imposta"; + btnSetTransparency.UseVisualStyleBackColor = true; // - // rdbLibrary2 + // chkUseTransparentColor // - rdbLibrary2.AutoSize = true; - rdbLibrary2.Location = new Point(12, 77); - rdbLibrary2.Name = "rdbLibrary2"; - rdbLibrary2.Size = new Size(149, 34); - rdbLibrary2.TabIndex = 1; - rdbLibrary2.TabStop = true; - rdbLibrary2.Text = "ImageSharp"; - rdbLibrary2.DataBindings.Add(new Binding("Checked", bindingSource1, "UseImageSharp", true, DataSourceUpdateMode.OnPropertyChanged)); - rdbLibrary2.UseVisualStyleBackColor = true; + chkUseTransparentColor.AutoSize = true; + chkUseTransparentColor.Location = new Point(39, 461); + chkUseTransparentColor.Name = "chkUseTransparentColor"; + chkUseTransparentColor.Size = new Size(210, 34); + chkUseTransparentColor.TabIndex = 46; + chkUseTransparentColor.Text = "Colore trasparente"; + chkUseTransparentColor.UseVisualStyleBackColor = true; // // MainForm // @@ -1905,6 +1900,8 @@ namespace ImageCatalog ((System.ComponentModel.ISupportInitialize)dataModelBindingSource).EndInit(); TabControl1.ResumeLayout(false); TabPage5.ResumeLayout(false); + groupBox12.ResumeLayout(false); + groupBox12.PerformLayout(); GroupBox11.ResumeLayout(false); GroupBox11.PerformLayout(); ((System.ComponentModel.ISupportInitialize)numericUpDown2).EndInit(); @@ -1939,12 +1936,9 @@ namespace ImageCatalog TabPage4.ResumeLayout(false); GroupBox6.ResumeLayout(false); GroupBox6.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)PictureBox2).EndInit(); ((System.ComponentModel.ISupportInitialize)_PictureBox1).EndInit(); - ((System.ComponentModel.ISupportInitialize)PictureBox3).EndInit(); ((System.ComponentModel.ISupportInitialize)dataModelBindingSource1).EndInit(); - groupBox12.ResumeLayout(false); - groupBox12.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)PictureBox3).EndInit(); ResumeLayout(false); PerformLayout(); } @@ -2145,7 +2139,6 @@ namespace ImageCatalog internal CheckBox CheckBox1; internal TabPage TabPage4; internal GroupBox GroupBox6; - internal PictureBox PictureBox2; private PictureBox _PictureBox1; internal PictureBox PictureBox1 @@ -2194,8 +2187,6 @@ namespace ImageCatalog internal TextBox TextBox10; internal Label Label29; - internal Label Label30; - internal PictureBox PictureBox3; private Label _Label27; internal Label Label27 @@ -2298,6 +2289,10 @@ namespace ImageCatalog private GroupBox groupBox12; private RadioButton rdbLibrary2; private RadioButton rdbLibrary1; + internal PictureBox PictureBox3; + private ColorDialog colorDialog1; + private Button btnSetTransparency; + private CheckBox chkUseTransparentColor; internal Button btnCreaCatalogoAsync { diff --git a/imagecatalog/MainForm.cs b/imagecatalog/MainForm.cs index 79574a4..684a60c 100644 --- a/imagecatalog/MainForm.cs +++ b/imagecatalog/MainForm.cs @@ -31,6 +31,7 @@ public partial class MainForm private readonly PicSettings _picSettings; // Prevent re-entrant updates between UI events and model PropertyChanged handling private bool _suppressRadioUpdates = false; + private bool _transparentDialogOpen = false; public MainForm(DataModel model, ImageCreationStuff imageCreationStuff, PicSettings picSettings, ParametriSetup parametriSetup, ILogger logger) @@ -89,9 +90,14 @@ public partial class MainForm btnOpenSourceFolder.Click += BtnOpenSourceFolder_Click; btnOpenDestFolder.Click += BtnOpenDestFolder_Click; + // Show currently selected color in small PictureBox3 + PictureBox3.BackColor = ColorTranslator.FromHtml(Model.TransparentColor); + // Version label is data-bound to DataModel.AppVersion; DataModel is populated with the version via DI } + + private void RdbLibrary_CheckedChanged(object? sender, EventArgs e) { // Keep behavior simple: when a radio button becomes checked, update the ViewModel @@ -142,6 +148,8 @@ public partial class MainForm _Button5.BindCommand(Model.SaveSettingsCommand); _Button6.BindCommand(Model.LoadSettingsCommand); _Button8.BindCommand(Model.SelectColorCommand); + // Bind the transparency chooser button/command + btnSetTransparency.BindCommand(Model.SelectTransparentColorCommand); // Subscribe to ViewModel events for UI dialogs (these need UI context) Model.SelectSourceFolderRequested += OnSelectSourceFolderRequested; @@ -150,10 +158,54 @@ public partial class MainForm Model.SaveSettingsRequested += OnSaveSettingsRequested; Model.LoadSettingsRequested += OnLoadSettingsRequested; Model.SelectColorRequested += OnSelectColorRequested; + Model.SelectTransparentColorRequested += OnSelectTransparentColorRequested; // Show message requests (from ViewModel validation) Model.ShowMessageRequested += OnShowMessageRequested; } + private void OnSelectTransparentColorRequested(object? sender, EventArgs e) + { + // Ensure UI thread + if (InvokeRequired) + { + Invoke(new Action(OnSelectTransparentColorRequested), sender, e as EventArgs ?? EventArgs.Empty); + return; + } + // Prevent re-entrancy: if the dialog is already open, ignore subsequent requests + if (_transparentDialogOpen) return; + + _transparentDialogOpen = true; + var dlg = new ColorDialog { AllowFullOpen = true }; + try + { + dlg.Color = ColorTranslator.FromHtml(Model.TransparentColor); + } + catch { } + try + { + if (dlg.ShowDialog() == DialogResult.OK) + { + Model.TransparentColor = ColorTranslator.ToHtml(dlg.Color); + PictureBox3.BackColor = dlg.Color; + + // Update preview if logo exists + if (!string.IsNullOrWhiteSpace(Model.LogoFile) && File.Exists(Model.LogoFile)) + { + UpdateLogoPictureBox(Model.LogoFile); + } + } + } + finally + { + _transparentDialogOpen = false; + } + } + + private void BtnSetTransparency_Click(object? sender, EventArgs e) + { + Model.SelectTransparentColorCommand.Execute(null); + } + private void OnShowMessageRequested(object? sender, Tuple args) { if (args is null) return; @@ -202,6 +254,64 @@ public partial class MainForm false, DataSourceUpdateMode.OnPropertyChanged)); Label10.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.ProcessingStatus), false, DataSourceUpdateMode.OnPropertyChanged)); + + // Bind transparency model properties to UI + chkUseTransparentColor.DataBindings.Add(new Binding("Checked", bindingSource1, nameof(Model.UseTransparentColor), false, DataSourceUpdateMode.OnPropertyChanged)); + // Show currently selected color in PictureBox3 + PictureBox3.Visible = false; + if (!string.IsNullOrWhiteSpace(Model.TransparentColor)) + { + try + { + PictureBox3.BackColor = ColorTranslator.FromHtml(Model.TransparentColor); + PictureBox3.Visible = true; + } + catch + { + PictureBox3.Visible = false; + } + } + + // When logo file changes, update preview + Model.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(Model.LogoFile) || e.PropertyName == nameof(Model.UseTransparentColor) || e.PropertyName == nameof(Model.TransparentColor)) + { + if (!string.IsNullOrWhiteSpace(Model.LogoFile) && System.IO.File.Exists(Model.LogoFile)) + { + UpdateLogoPictureBox(Model.LogoFile); + } + } + }; + + // Bind transparent color hex and show color in PictureBox3 + // Bind UseTransparentColor checkbox (designer control named CheckBox5 used for AddLogo earlier, add new binding control exists in designer) + // Use PictureBox3 to display color value + var colorBinding = new Binding("BackColor", bindingSource1, nameof(Model.TransparentColor), true, DataSourceUpdateMode.OnPropertyChanged); + colorBinding.Format += (s, e) => + { + try + { + e.Value = ColorTranslator.FromHtml(e.Value?.ToString() ?? "#FFFFFF"); + } + catch + { + e.Value = Color.White; + } + }; + PictureBox3.DataBindings.Add(colorBinding); + + // Bind checkbox for using color key transparency if such control exists (CheckBox5 was repurposed as AddLogo); create binding if available + try + { + // The designer has CheckBox5 for 'AddLogo'. We'll add a separate binding to a new control named CheckBoxUseTransparentColor if present. + var chk = this.Controls.Find("chkUseTransparentColor", true).FirstOrDefault() as CheckBox; + if (chk != null) + { + chk.DataBindings.Add(new Binding("Checked", bindingSource1, nameof(Model.UseTransparentColor), false, DataSourceUpdateMode.OnPropertyChanged)); + } + } + catch { } } @@ -212,6 +322,18 @@ public partial class MainForm SetDefaults(); _logger.LogInformation("Programma Avviato"); + // If settings were loaded before the form was shown, ensure the logo preview is updated + try + { + if (!string.IsNullOrWhiteSpace(Model.LogoFile) && File.Exists(Model.LogoFile)) + { + UpdateLogoPictureBox(Model.LogoFile); + } + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Failed to load logo during form load"); + } } private string CalcTime(DateTime timeStart, DateTime timeStop, int numFoto) @@ -436,14 +558,50 @@ public partial class MainForm try { await Model.LoadSettingsFromFileAsync(openDialog.FileName); - + // Explicitly ensure UI is enabled after loading Model.UiEnabled = true; - - // Update logo preview if logo file exists - if (File.Exists(Model.LogoFile)) + + // If a logo path was stored in the settings, try to resolve and display it. + // The stored path may be absolute or relative to the settings file location. + try { - UpdateLogoPictureBox(Model.LogoFile); + var storedLogo = Model.LogoFile; + // Trim whitespace and surrounding quotes which may be present in saved settings + if (!string.IsNullOrWhiteSpace(storedLogo)) + { + storedLogo = storedLogo.Trim(); + storedLogo = storedLogo.Trim('"'); + } + if (!string.IsNullOrWhiteSpace(storedLogo)) + { + string resolved = storedLogo; + // If not rooted, try to resolve relative to the settings file folder + var settingsFolder = Path.GetDirectoryName(openDialog.FileName) ?? string.Empty; + if (!Path.IsPathRooted(resolved) && !string.IsNullOrWhiteSpace(settingsFolder)) + { + var candidate = Path.Combine(settingsFolder, resolved); + if (File.Exists(candidate)) resolved = candidate; + } + + // If rooted but file doesn't exist, try filename near settings file + if (!File.Exists(resolved) && !string.IsNullOrWhiteSpace(settingsFolder)) + { + var candidate2 = Path.Combine(settingsFolder, Path.GetFileName(resolved)); + if (File.Exists(candidate2)) resolved = candidate2; + } + + if (File.Exists(resolved)) + { + // Update the model so data-bound controls reflect the resolved absolute path + Model.LogoFile = resolved; + UpdateLogoPictureBox(resolved); + } + } + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error resolving logo path after loading settings"); } Text = "Image Catalog - " + Path.GetFileName(openDialog.FileName); @@ -519,17 +677,68 @@ public partial class MainForm { try { - PictureBox1.Image = Image.FromFile(logoPath); - if (PictureBox1.Image.Height >= PictureBox1.Image.Width) + // Load image via System.Drawing for preview so we can use MakeTransparent when requested + using var img = System.Drawing.Image.FromFile(logoPath); + + System.Drawing.Bitmap previewBmp; + // If using color-key transparency and a color is selected, apply MakeTransparent for preview + if (Model.UseTransparentColor && !string.IsNullOrWhiteSpace(Model.TransparentColor)) { - PictureBox1.Height = 160; - PictureBox1.Width = (int)(160 * PictureBox1.Image.Width / (double)PictureBox1.Image.Height); + try + { + var key = ColorTranslator.FromHtml(Model.TransparentColor); + previewBmp = new System.Drawing.Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (var g = System.Drawing.Graphics.FromImage(previewBmp)) + { + g.Clear(System.Drawing.Color.Transparent); + g.DrawImage(img, 0, 0, img.Width, img.Height); + } + // Apply exact color-key transparency + previewBmp.MakeTransparent(key); + PictureBox3.BackColor = key; + PictureBox3.Visible = true; + } + catch + { + previewBmp = new System.Drawing.Bitmap(img); + } } else { - PictureBox1.Width = 160; - PictureBox1.Height = (int)(160 * PictureBox1.Image.Height / (double)PictureBox1.Image.Width); + previewBmp = new System.Drawing.Bitmap(img); } + + // Resize preview to fit into PictureBox1 while preserving aspect ratio + // Resize preview to fit into PictureBox1 while preserving aspect ratio + var boxW = PictureBox1.ClientSize.Width > 0 ? PictureBox1.ClientSize.Width : 449; + var boxH = PictureBox1.ClientSize.Height > 0 ? PictureBox1.ClientSize.Height : 369; + var ratio = Math.Min((double)boxW / previewBmp.Width, (double)boxH / previewBmp.Height); + var destW = Math.Max(1, (int)(previewBmp.Width * ratio)); + var destH = Math.Max(1, (int)(previewBmp.Height * ratio)); + var scaled = new System.Drawing.Bitmap(destW, destH, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (var g = System.Drawing.Graphics.FromImage(scaled)) + { + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.Clear(System.Drawing.Color.Transparent); + // Center the image in the PictureBox + var offsetX = Math.Max(0, (boxW - destW) / 2); + var offsetY = Math.Max(0, (boxH - destH) / 2); + using var canvas = new System.Drawing.Bitmap(boxW, boxH, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (var cg = System.Drawing.Graphics.FromImage(canvas)) + { + cg.Clear(System.Drawing.Color.Transparent); + cg.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + cg.DrawImage(previewBmp, offsetX, offsetY, destW, destH); + } + g.DrawImage(canvas, 0, 0); + } + + // Set PictureBox1 image (dispose previous) + var old = PictureBox1.Image; + PictureBox1.SizeMode = PictureBoxSizeMode.CenterImage; + PictureBox1.Image = scaled; + old?.Dispose(); + previewBmp.Dispose(); } catch { @@ -537,6 +746,39 @@ public partial class MainForm } } + private void UpdateLogoPreviewWithColorKey(string logoPath, Color keyColor) + { + try + { + using var img = (Bitmap)Image.FromFile(logoPath); + // Create ARGB copy + var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (var g = Graphics.FromImage(bmp)) + { + g.DrawImage(img, 0, 0, img.Width, img.Height); + } + + bmp.MakeTransparent(keyColor); + + // Resize to PictureBox1 size similar to previous logic + Bitmap finalBmp; + if (bmp.Height >= bmp.Width) + { + finalBmp = new Bitmap(bmp, new Size((int)(160 * bmp.Width / (double)bmp.Height), 160)); + } + else + { + finalBmp = new Bitmap(bmp, new Size(160, (int)(160 * bmp.Height / (double)bmp.Width))); + } + + PictureBox1.Image = finalBmp; + } + catch + { + // ignore preview failures + } + } + private void setLabel18Text(string text) { if (Label18.InvokeRequired) diff --git a/imagecatalog/MainForm.resx b/imagecatalog/MainForm.resx index a4e857a..fa95561 100644 --- a/imagecatalog/MainForm.resx +++ b/imagecatalog/MainForm.resx @@ -129,4 +129,7 @@ 349, 17 + + 802, 17 + \ No newline at end of file diff --git a/imagecatalog/Models/SettingsDto.cs b/imagecatalog/Models/SettingsDto.cs index 70896e0..e31eb2a 100644 --- a/imagecatalog/Models/SettingsDto.cs +++ b/imagecatalog/Models/SettingsDto.cs @@ -168,6 +168,20 @@ namespace ImageCatalog_2.Models [XmlElement("MarchioAggiungi")] public bool AddLogo { get; set; } + // Color-key transparency settings + [JsonPropertyName("TransparentColor")] + [XmlElement("ColoreTrasparente")] + public string TransparentColor { get; set; } = "#FFFFFF"; + + [JsonPropertyName("UseTransparentColor")] + [XmlElement("UsaColoreTrasparente")] + public bool UseTransparentColor { get; set; } = false; + + // Selected image processing library (e.g., "System.Graphics" or "ImageSharp") + [JsonPropertyName("ImageLibrary")] + [XmlElement("ImageLibrary")] + public string ImageLibrary { get; set; } = "System.Graphics"; + // Options [JsonPropertyName("ForceJpeg")] [XmlElement("GeneraleForzaJpg")] diff --git a/imagecatalog/Services/SettingsService.cs b/imagecatalog/Services/SettingsService.cs index 8a92333..272566f 100644 --- a/imagecatalog/Services/SettingsService.cs +++ b/imagecatalog/Services/SettingsService.cs @@ -85,15 +85,15 @@ namespace ImageCatalog_2.Services } else if (prop.PropertyType == typeof(int)) { - value = _parametriSetup.LeggiParametro(xmlName, (int)(prop.GetValue(loadedDto) ?? 0)); + value = fileParams.LeggiParametro(xmlName, (int)(prop.GetValue(loadedDto) ?? 0)); } else if (prop.PropertyType == typeof(double)) { - value = _parametriSetup.LeggiParametro(xmlName, (double)(prop.GetValue(loadedDto) ?? 0.0)); + value = fileParams.LeggiParametro(xmlName, (double)(prop.GetValue(loadedDto) ?? 0.0)); } else if (prop.PropertyType == typeof(DateTime)) { - var strValue = _parametriSetup.LeggiParametroString(xmlName); + var strValue = fileParams.LeggiParametroString(xmlName); if (DateTime.TryParse(strValue, out var dateValue)) { value = dateValue;