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
|
|
@ -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<Rgba32> 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<Rgba32> 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<Rgba32>(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<Rgba32>(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -486,22 +486,37 @@ public class ImageCreatorSharp(PicSettings picSettings, ILogger<ImageCreatorShar
|
|||
// Load check (use short-circuit &&)
|
||||
if (!(picSettings.LogoAggiungi && File.Exists(picSettings.LogoNomeFile))) return;
|
||||
|
||||
var logoColoreTrasparente = Color.White;
|
||||
|
||||
// * Load this Bitmap into a new Graphic Object
|
||||
// Decide whether to apply a color-key transparency remap or rely on existing image alpha.
|
||||
// If UseTransparentColor is true, parse the configured TransparentColor and remap it to fully transparent.
|
||||
using var grWatermark = Graphics.FromImage(imgOutputBig);
|
||||
using ImageAttributes imageAttributes = new ImageAttributes();
|
||||
|
||||
// * The first step replace the background color with one that is transparent (Alpha=0, R=0, G=0, B=0)
|
||||
var colorMap = new ColorMap
|
||||
if (picSettings.UseTransparentColor)
|
||||
{
|
||||
// * background this will be the color we search for and replace with transparency
|
||||
OldColor = logoColoreTrasparente,
|
||||
NewColor = Color.FromArgb(0, 0, 0, 0)
|
||||
};
|
||||
Color keyColor = Color.White;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(picSettings.TransparentColor))
|
||||
{
|
||||
// ColorTranslator accepts both "#RRGGBB" and "RRGGBB"
|
||||
keyColor = ColorTranslator.FromHtml(picSettings.TransparentColor);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
keyColor = Color.White;
|
||||
}
|
||||
|
||||
var remapTable = new[] { colorMap };
|
||||
imageAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);
|
||||
var colorMap = new ColorMap
|
||||
{
|
||||
// background: the color we search for and replace with transparency
|
||||
OldColor = keyColor,
|
||||
NewColor = Color.FromArgb(0, 0, 0, 0)
|
||||
};
|
||||
|
||||
var remapTable = new[] { colorMap };
|
||||
imageAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);
|
||||
}
|
||||
|
||||
// * The second color manipulation is used to change the opacity by setting the 3rd row and 3rd column to 0.3f
|
||||
// Parse transparency safely (default to 100 if parsing fails)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,22 @@ public class PicSettings
|
|||
public string LogoMargine { get; set; } = "0";
|
||||
public string LogoPosizioneH { get; set; }
|
||||
public string LogoPosizioneV { get; set; }
|
||||
// Color key transparency: hex color string (e.g. #FFFFFF) and whether to use it for non-PNG logos
|
||||
public string TransparentColorHex { get; set; } = "#FFFFFF";
|
||||
public bool UseTransparentColorKey { get; set; } = false;
|
||||
|
||||
// Aliases to support automatic mapping from DataModel properties
|
||||
public string TransparentColor
|
||||
{
|
||||
get => 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; }
|
||||
|
|
|
|||
|
|
@ -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<DataModel> _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<Tuple<string, string, MessageBoxIcon>> 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);
|
||||
|
|
|
|||
169
imagecatalog/MainForm.Designer.cs
generated
169
imagecatalog/MainForm.Designer.cs
generated
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -129,4 +129,7 @@
|
|||
<metadata name="dataModelBindingSource1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>349, 17</value>
|
||||
</metadata>
|
||||
<metadata name="colorDialog1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>802, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -85,15 +85,15 @@ namespace ImageCatalog_2.Services
|
|||
}
|
||||
else if (prop.PropertyType == typeof(int))
|
||||
{
|
||||
value = _parametriSetup.LeggiParametro<int>(xmlName, (int)(prop.GetValue(loadedDto) ?? 0));
|
||||
value = fileParams.LeggiParametro<int>(xmlName, (int)(prop.GetValue(loadedDto) ?? 0));
|
||||
}
|
||||
else if (prop.PropertyType == typeof(double))
|
||||
{
|
||||
value = _parametriSetup.LeggiParametro<double>(xmlName, (double)(prop.GetValue(loadedDto) ?? 0.0));
|
||||
value = fileParams.LeggiParametro<double>(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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue