using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Text; using System.IO; using System.Linq; using System.Reflection; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using ImageCatalog_2; using ImageCatalog_2.Commands; using ImageCatalog_2.Services; using MaddoShared; using Microsoft.Extensions.Logging; namespace ImageCatalog; public partial class MainForm { private readonly DataModel Model; private readonly ILogger _logger; private readonly ParametriSetup _parametriSetup; 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, ImageCreationService imageCreationStuff, PicSettings picSettings, ParametriSetup parametriSetup, ILogger logger) { Model = model; _parametriSetup = parametriSetup; _picSettings = picSettings; _logger = logger; _logger.LogDebug("Start"); InitializeComponent(); // Set this form as the control for thread marshalling in the DataModel Model.SetControl(this); // Ensure the designer data bindings have a concrete DataSource immediately so // that UI controls (radio buttons) reflect the ViewModel state and propagate // user changes back to the ViewModel. bindingSource1.DataSource = Model; BindControls(); // The designer originally bound the radio buttons to boolean helpers on the ViewModel. // Those two separate bindings can fight with each other. Clear designer bindings and // wire explicit Click handlers that update the single authoritative property // `Model.ImageLibrary`. Also keep a PropertyChanged listener to reflect external // changes back into the radio buttons. rdbLibrary1.DataBindings.Clear(); rdbLibrary2.DataBindings.Clear(); // Initialize radio state from model rdbLibrary1.Checked = Model.UseSystemGraphics; rdbLibrary2.Checked = Model.UseImageSharp; // Use Click handlers (not CheckedChanged) to avoid competing binding updates rdbLibrary1.Click += (_, _) => { if (_suppressRadioUpdates) return; if (Model.ImageLibrary != "System.Graphics") Model.ImageLibrary = "System.Graphics"; }; rdbLibrary2.Click += (_, _) => { if (_suppressRadioUpdates) return; if (Model.ImageLibrary != "ImageSharp") Model.ImageLibrary = "ImageSharp"; }; // Watch for model changes so we can reflect external updates Model.PropertyChanged += Model_PropertyChanged; // Thumbnail options moved to ComboBox to avoid conflicting bindings with multiple radio buttons // Initialize ComboBox with Italian descriptions comboThumbnailOption.Items.Clear(); comboThumbnailOption.Items.AddRange(new object[] { "Nessuna", "Aggiungi scritta", "Nome file", "Aggiungi orario", "Nome+Orario", "Tempo gara" }); // Bind to model via helper index property ThumbnailOptionIndex comboThumbnailOption.DataBindings.Add(new Binding("SelectedIndex", bindingSource1, "ThumbnailOptionIndex", true, DataSourceUpdateMode.OnPropertyChanged)); // Save user preferences on form close instead of immediately when dialogs are used this.FormClosing += MainForm_FormClosing; // Wire up 'Open folder in Explorer' buttons 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 // so that the designer binding and PicSettings stay in sync. if (sender is RadioButton rb && rb.Checked) { _logger?.LogDebug("Radio library changed: {RadioName}", rb.Name); if (rb == rdbLibrary2) { Model.ImageLibrary = "ImageSharp"; } else if (rb == rdbLibrary1) { Model.ImageLibrary = "System.Graphics"; } } } private void Model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName is null) return; if (e.PropertyName == nameof(Model.ImageLibrary) || e.PropertyName == nameof(Model.UseImageSharp) || e.PropertyName == nameof(Model.UseSystemGraphics)) { _logger?.LogDebug("Model property changed: {Property} => ImageLibrary={ImageLibrary}, PicSettings.Provider={Provider}", e.PropertyName, Model.ImageLibrary, _picSettings.ImageCreatorProvider); // Reflect authoritative model value into the radio buttons in a thread-safe, re-entrancy-safe way try { _suppressRadioUpdates = true; rdbLibrary1.Checked = Model.UseSystemGraphics; rdbLibrary2.Checked = Model.UseImageSharp; } finally { _suppressRadioUpdates = false; } } // Thumbnail mode changes - reflect back to combo box index if (e.PropertyName == nameof(Model.ThumbnailMode) || e.PropertyName == nameof(Model.ThumbnailOption) || e.PropertyName == nameof(Model.ThumbnailOptionIndex)) { try { _suppressRadioUpdates = true; comboThumbnailOption.SelectedIndex = Model.ThumbnailOptionIndex; } finally { _suppressRadioUpdates = false; } } } protected void BindControls() { // Bind buttons to ViewModel commands using command binding _btnCreaCatalogoAsync.BindCommand(Model.ProcessImagesCommand); // Note: `button1` control does not exist in the designer. Use the primary create button only. _Button2.BindCommand(Model.SelectSourceFolderCommand); _Button3.BindCommand(Model.SelectDestinationFolderCommand); _Button4.BindCommand(Model.SelectLogoFileCommand); _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; Model.SelectDestinationFolderRequested += OnSelectDestinationFolderRequested; Model.SelectLogoFileRequested += OnSelectLogoFileRequested; 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; // Ensure call on UI thread if (InvokeRequired) { Invoke(new Action(() => OnShowMessageRequested(sender, args))); return; } MessageBox.Show(this, args.Item1, args.Item2, MessageBoxButtons.OK, args.Item3); } private void SetDefaults() { // Bind ComboBoxes to Model using proper data binding ComboBox1.DataSource = new List(Model.VerticalPositions); ComboBox1.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.VerticalPosition), false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox2.DataSource = new List(Model.HorizontalAlignments); ComboBox2.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.HorizontalAlignment), false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox3.DataSource = new List(Model.AvailableFonts); ComboBox3.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.FontName), false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox4.DataSource = new List(Model.HorizontalAlignments); ComboBox4.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoHorizontalPosition), false, DataSourceUpdateMode.OnPropertyChanged)); ComboBox5.DataSource = new List { "Alto", "Centro", "Basso" }; ComboBox5.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoVerticalPosition), false, DataSourceUpdateMode.OnPropertyChanged)); // Bind progress bar and status labels ProgressBar1.DataBindings.Add(new Binding("Maximum", bindingSource1, nameof(Model.ProgressBarMaximum), false, DataSourceUpdateMode.OnPropertyChanged)); ProgressBar1.DataBindings.Add(new Binding("Value", bindingSource1, nameof(Model.ProgressBarValue), false, DataSourceUpdateMode.OnPropertyChanged)); Label18.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.ProcessedImagesCount), false, DataSourceUpdateMode.OnPropertyChanged)); lblFotoTotaliNum.DataBindings.Add(new Binding("Text", bindingSource1, nameof(Model.TotalImagesCount), 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 { } } private void Form1_Load(object sender, EventArgs e) { bindingSource1.DataSource = Model; Application.EnableVisualStyles(); 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) { long timediffH, timediffS; long timediffM; TimeSpan timeDiff = timeStop - timeStart; timediffM = (int)timeDiff.TotalMinutes; timediffS = (int)timeDiff.TotalSeconds; timediffH = (int)timeDiff.TotalHours; // timediffM = DateAndTime.DateDiff(DateInterval.Minute, timeStart, timeStop); // timediffS = DateAndTime.DateDiff(DateInterval.Second, timeStart, timeStop); // timediffH = DateAndTime.DateDiff(DateInterval.Hour, timeStart, timeStop); double fotoSec = numFoto / (double)timediffS; double fotoMin = numFoto / (double)timediffM; double fotoOra = numFoto / (double)timediffH; string s = "S: " + timediffS.ToString() + "; F/s: " + fotoSec.ToString( "0.000"); // + " F/m: " + fotoMin.ToString("0.00") + " F/h: " + fotoOra.ToString("0.00") return s; } private string SelectFolder(string startingFolder) { var dialog = new FolderBrowserDialog(); dialog.InitialDirectory = startingFolder; if (dialog.ShowDialog() != DialogResult.OK) return string.Empty; return FixPath(dialog.SelectedPath); } private string FixPath(string path) { if (string.IsNullOrWhiteSpace(path)) { return string.Empty; } // Trim leading/trailing quotes path = path.Trim().Trim('"'); // Normalize directory separators path = path.Replace('/', Path.DirectorySeparatorChar) .Replace('\\', Path.DirectorySeparatorChar); // Remove trailing separators then add one back path = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; return path; } private void OnSelectSourceFolderRequested(object sender, EventArgs e) { // Prefer model value; if empty fall back to last-used value stored in user prefs var starting = !string.IsNullOrWhiteSpace(Model.SourcePath) ? Model.SourcePath : _parametriSetup.LeggiParametroString("LastSourceFolder"); var dialogResult = SelectFolder(starting); if (!string.IsNullOrWhiteSpace(dialogResult)) { Model.SourcePath = dialogResult; _parametriSetup.AggiornaParametro("LastSourceFolder", dialogResult); _parametriSetup.SalvaParametriSetup(); } } private void BtnOpenSourceFolder_Click(object? sender, EventArgs e) { // Prefer the model value but fall back to the textbox if needed var path = string.IsNullOrWhiteSpace(Model.SourcePath) ? txtSorgente.Text : Model.SourcePath; OpenFolder(path); } private void BtnOpenDestFolder_Click(object? sender, EventArgs e) { var path = string.IsNullOrWhiteSpace(Model.DestinationPath) ? txtDestinazione.Text : Model.DestinationPath; OpenFolder(path); } private void OpenFolder(string? path) { if (string.IsNullOrWhiteSpace(path)) { MessageBox.Show(this, "Folder path is empty.", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } path = path.Trim().Trim('"'); try { if (File.Exists(path)) { // If a file was provided, open its folder and select it Process.Start("explorer.exe", $"/select,\"{path}\""); return; } if (Directory.Exists(path)) { Process.Start(new ProcessStartInfo { FileName = path, UseShellExecute = true }); return; } MessageBox.Show(this, $"Folder does not exist: {path}", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Warning); } catch (Exception ex) { _logger?.LogError(ex, "Failed to open folder {Path}", path); MessageBox.Show(this, $"Failed to open folder: {ex.Message}", "Open Folder", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void OnSelectDestinationFolderRequested(object sender, EventArgs e) { var starting = !string.IsNullOrWhiteSpace(Model.DestinationPath) ? Model.DestinationPath : _parametriSetup.LeggiParametroString("LastDestinationFolder"); var dialogResult = SelectFolder(starting); if (!string.IsNullOrWhiteSpace(dialogResult)) { Model.DestinationPath = dialogResult; _parametriSetup.AggiornaParametro("LastDestinationFolder", dialogResult); _parametriSetup.SalvaParametriSetup(); } } private void OnSelectLogoFileRequested(object sender, EventArgs e) { var dialog = new OpenFileDialog(); dialog.Filter = "Image Files|*.jpg;*.jpeg;*.png;*.bmp;*.gif"; if (!string.IsNullOrWhiteSpace(Model.LogoFile)) { dialog.FileName = Model.LogoFile; } else { var lastLogoFolder = _parametriSetup.LeggiParametroString("LastLogoFolder"); if (!string.IsNullOrWhiteSpace(lastLogoFolder) && Directory.Exists(lastLogoFolder)) { dialog.InitialDirectory = lastLogoFolder; } } if (dialog.ShowDialog() == DialogResult.OK) { Model.LogoFile = dialog.FileName; UpdateLogoPictureBox(Model.LogoFile); try { var folder = Path.GetDirectoryName(dialog.FileName) ?? string.Empty; if (!string.IsNullOrWhiteSpace(folder)) { _parametriSetup.AggiornaParametro("LastLogoFolder", folder); _parametriSetup.SalvaParametriSetup(); } } catch { // ignore preferences save failures } } } private async void OnSaveSettingsRequested(object sender, string e) { var saveDialog = new SaveFileDialog { Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*", FilterIndex = 0, RestoreDirectory = true }; var lastSettings = _parametriSetup.LeggiParametroString("LastSettingsFolder"); if (!string.IsNullOrWhiteSpace(lastSettings) && Directory.Exists(lastSettings)) saveDialog.InitialDirectory = lastSettings; if (saveDialog.ShowDialog() != DialogResult.OK) return; await Model.SaveSettingsToFileAsync(saveDialog.FileName); Text = "Image Catalog - " + Path.GetFileName(saveDialog.FileName); try { var folder = Path.GetDirectoryName(saveDialog.FileName) ?? string.Empty; if (!string.IsNullOrWhiteSpace(folder)) { _parametriSetup.AggiornaParametro("LastSettingsFolder", folder); _parametriSetup.SalvaParametriSetup(); } } catch { // ignore } } private async void OnLoadSettingsRequested(object sender, string e) { var openDialog = new OpenFileDialog { Filter = "Setup (*.xml)|*.xml|All valid files (*.*)|*.*", FilterIndex = 0, RestoreDirectory = true }; var lastSettings = _parametriSetup.LeggiParametroString("LastSettingsFolder"); if (!string.IsNullOrWhiteSpace(lastSettings) && Directory.Exists(lastSettings)) openDialog.InitialDirectory = lastSettings; if (openDialog.ShowDialog() != DialogResult.OK) return; try { await Model.LoadSettingsFromFileAsync(openDialog.FileName); // Explicitly ensure UI is enabled after loading Model.UiEnabled = true; // 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 { 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); try { var folder = Path.GetDirectoryName(openDialog.FileName) ?? string.Empty; if (!string.IsNullOrWhiteSpace(folder)) { _parametriSetup.AggiornaParametro("LastSettingsFolder", folder); _parametriSetup.SalvaParametriSetup(); } } catch { // ignore preferences save failures } _logger.LogInformation($"Settings loaded successfully from {openDialog.FileName}"); } catch (Exception ex) { _logger.LogError(ex, "Error loading settings"); MessageBox.Show($"Error loading settings: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void MainForm_FormClosing(object? sender, FormClosingEventArgs e) { try { // Persist last-used dialogs paths (user preferences) // These keys are managed independently from settings files // and must be saved when the form closes. _parametriSetup.AggiornaParametro("LastSourceFolder", Model.SourcePath ?? string.Empty); _parametriSetup.AggiornaParametro("LastDestinationFolder", Model.DestinationPath ?? string.Empty); _parametriSetup.AggiornaParametro("LastLogoFolder", Path.GetDirectoryName(Model.LogoFile ?? string.Empty) ?? string.Empty); _parametriSetup.SalvaParametriSetup(); } catch (Exception ex) { _logger?.LogWarning(ex, "Failed to save user preferences on exit"); } } private void OnSelectColorRequested(object sender, EventArgs e) { var colorDialog = new ColorDialog { AllowFullOpen = true }; if (!string.IsNullOrWhiteSpace(Model.TextColorRGB)) { try { colorDialog.Color = ColorTranslator.FromHtml(Model.TextColorRGB); } catch { // Invalid color, use default } } if (colorDialog.ShowDialog() == DialogResult.OK) { Model.TextColorRGB = ColorTranslator.ToHtml(colorDialog.Color); TextBox34.BackColor = colorDialog.Color; } } private void UpdateLogoPictureBox(string logoPath) { try { // 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)) { 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 { 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 { // Image loading failed, ignore } } 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) { Label18.Invoke(new Action(setLabel18Text), text); } else { Label18.Text = text; } } } public class PicInfo { public DirectoryInfo DirSource, DirDest, DirDestStart; public string NomeImmagine; public PicInfo(DirectoryInfo Dir_Source, DirectoryInfo Dir_Dest, DirectoryInfo Dir_DestStart, string Nome_Immagine) { DirSource = Dir_Source; DirDest = Dir_Dest; DirDestStart = Dir_DestStart; NomeImmagine = Nome_Immagine; } }