Catalog/imagecatalog/MainForm.cs
MaddoScientisto d13ec8abdf Refactor thumbnail options to enum and ComboBox UI
Replaced multiple mutually-exclusive boolean properties for thumbnail text options with a single enum (`ThumbnailOption`) in the data model. Updated WinForms UI to use a ComboBox instead of radio buttons for selecting thumbnail mode. Adjusted designer, mapping profile, settings DTO, and settings service for enum support and backward compatibility. Simplified thumbnail generation logic and improved maintainability by ensuring only one mode can be selected at a time.
2026-02-16 19:55:37 +01:00

840 lines
No EOL
32 KiB
C#

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<MainForm> _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, ImageCreationStuff imageCreationStuff, PicSettings picSettings,
ParametriSetup parametriSetup, ILogger<MainForm> 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<object, EventArgs>(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<string, string, MessageBoxIcon> 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<string>(Model.VerticalPositions);
ComboBox1.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.VerticalPosition),
false, DataSourceUpdateMode.OnPropertyChanged));
ComboBox2.DataSource = new List<string>(Model.HorizontalAlignments);
ComboBox2.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.HorizontalAlignment),
false, DataSourceUpdateMode.OnPropertyChanged));
ComboBox3.DataSource = new List<string>(Model.AvailableFonts);
ComboBox3.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.FontName),
false, DataSourceUpdateMode.OnPropertyChanged));
ComboBox4.DataSource = new List<string>(Model.HorizontalAlignments);
ComboBox4.DataBindings.Add(new Binding("SelectedItem", bindingSource1, nameof(Model.LogoHorizontalPosition),
false, DataSourceUpdateMode.OnPropertyChanged));
ComboBox5.DataSource = new List<string> { "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<string>(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;
}
}