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.
This commit is contained in:
parent
63751af18d
commit
8872080741
9 changed files with 732 additions and 168 deletions
|
|
@ -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<MainForm> 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<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;
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue