Implement ImageCreatorImageSharp using SixLabors.ImageSharp for image processing

- Added ImageCreatorImageSharp class for image creation, handling EXIF orientation, resizing, and saving images.
- Replaced GDI+ dependencies with ImageSharp for cross-platform compatibility.
- Introduced methods for drawing text and logos on images, including handling transparency and positioning.
- Created a test plan for validating ImageCreatorImageSharp functionality, focusing on image resizing, text positioning, logo features, and EXIF orientation.
- Added documentation for the test plan outlining goals, project structure, and implementation notes.
This commit is contained in:
MaddoScientisto 2026-03-08 11:17:47 +01:00
commit d62342aae1
11 changed files with 455 additions and 74 deletions

View file

@ -228,23 +228,23 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
}
}
private void CreateThumbnails(Image sourceImage, ImageState imgState, Bitmap imgOutputBig, ImageFormat format)
{
// Only skip thumbnail generation when the global "create thumbnails" flag is false.
// Whether thumbnails include text is handled by ShouldRenderText/CreateThumbnailWithText
if (!picSettings.CreaMiniature)
return;
private void CreateThumbnails(Image sourceImage, ImageState imgState, Bitmap imgOutputBig, ImageFormat format)
{
// Only skip thumbnail generation when the global "create thumbnails" flag is false.
// Whether thumbnails include text is handled by ShouldRenderText/CreateThumbnailWithText
if (!picSettings.CreaMiniature)
return;
PrepareSignatureText(imgState);
PrepareSignatureText(imgState);
if (IsSameDirectory(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione))
UpdateFilenameWithCode(imgState);
if (IsSameDirectory(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione))
UpdateFilenameWithCode(imgState);
if (ShouldRenderText())
CreateThumbnailWithText(sourceImage, imgState, imgOutputBig, format);
else
CreateSimpleThumbnail(sourceImage, imgState, format);
}
if (ShouldRenderText())
CreateThumbnailWithText(sourceImage, imgState, imgOutputBig, format);
else
CreateSimpleThumbnail(sourceImage, imgState, format);
}
private void PrepareSignatureText(ImageState imgState)
{
@ -294,7 +294,7 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
// This leaves room for margins and prevents clipping
int tempFontSize = imgState.DimensioneStandardMiniatura;
float maxTextHeight = image.Height * 0.15f;
while ((textSize.Width > image.Width * 0.95f || textSize.Height > maxTextHeight) && tempFontSize > 5)
{
tempFontSize = (tempFontSize > 20) ? tempFontSize - 5 : tempFontSize - 1;
@ -375,20 +375,20 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
switch (picSettings.Posizione.ToUpper())
{
case "ALTO":
{
imgState.YPosFromBottom = picSettings.Margine;
imgState.YPosFromBottom3 = picSettings.MargVert;
break;
}
{
imgState.YPosFromBottom = picSettings.Margine;
imgState.YPosFromBottom3 = picSettings.MargVert;
break;
}
case "BASSO":
{
imgState.YPosFromBottom =
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.Margine / 100.0)));
imgState.YPosFromBottom3 =
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.MargVert / 100.0)));
break;
}
{
imgState.YPosFromBottom =
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.Margine / 100.0)));
imgState.YPosFromBottom3 =
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.MargVert / 100.0)));
break;
}
}
float xCenterOfImg = 0;
@ -396,27 +396,27 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
switch (picSettings.Allineamento.ToUpper())
{
case "SINISTRA":
{
xCenterOfImg = Convert.ToSingle((picSettings.Margine + (larghezzaStandard / (double)2)));
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
break;
}
{
xCenterOfImg = Convert.ToSingle((picSettings.Margine + (larghezzaStandard / (double)2)));
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
break;
}
case "CENTRO":
{
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
break;
}
{
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
break;
}
case "DESTRA":
{
xCenterOfImg =
Convert.ToSingle((g.Width - picSettings.Margine - (larghezzaStandard / (double)2)));
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
break;
}
{
xCenterOfImg =
Convert.ToSingle((g.Width - picSettings.Margine - (larghezzaStandard / (double)2)));
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
break;
}
}
strFormat.Alignment = StringAlignment.Center;
@ -528,7 +528,7 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
{
logoTransparencyValue = 100;
}
var colorMatrixElements = new[]
{
new[] { 1.0F, 0.0F, 0.0F, 0.0F, 0.0F }, new[] { 0.0F, 1.0F, 0.0F, 0.0F, 0.0F },
@ -571,44 +571,44 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
{
case "SINISTRA":
case "NESSUNA":
{
xPosOfWm = margineUsato;
break;
}
{
xPosOfWm = margineUsato;
break;
}
case "CENTRO":
{
xPosOfWm = System.Convert.ToInt32((imgOutputBig.Width - nuovaSize.Width) / (double)2);
break;
}
{
xPosOfWm = System.Convert.ToInt32((imgOutputBig.Width - nuovaSize.Width) / (double)2);
break;
}
case "DESTRA":
{
xPosOfWm = ((imgOutputBig.Width - nuovaSize.Width) - margineUsato);
break;
}
{
xPosOfWm = ((imgOutputBig.Width - nuovaSize.Width) - margineUsato);
break;
}
}
switch (logoV)
{
case "ALTO":
case "NESSUNA":
{
yPosOfWm = margineUsato;
break;
}
{
yPosOfWm = margineUsato;
break;
}
case "CENTRO":
{
yPosOfWm = System.Convert.ToInt32((imgOutputBig.Height - nuovaSize.Height) / (double)2);
break;
}
{
yPosOfWm = System.Convert.ToInt32((imgOutputBig.Height - nuovaSize.Height) / (double)2);
break;
}
case "BASSO":
{
yPosOfWm = ((imgOutputBig.Height - nuovaSize.Height) - margineUsato);
break;
}
{
yPosOfWm = ((imgOutputBig.Height - nuovaSize.Height) - margineUsato);
break;
}
}
grWatermark.DrawImage(logo, new Rectangle(xPosOfWm, yPosOfWm, nuovaSize.Width, nuovaSize.Height), 0, 0,
@ -776,7 +776,7 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
{
// Use 1% of image height as minimum margin, or 10px, whichever is larger
float minMargin = Math.Max(10f, imgHeight * 0.01f);
switch (picSettings.Posizione.ToUpper())
{
case "ALTO":
@ -787,18 +787,18 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
case "BASSO":
var bottomMargin1 = (float)(imgHeight * picSettings.Margine / 100.0);
var bottomMargin4 = (float)(imgHeight * picSettings.MargVert / 100.0);
// Position from bottom: bottom edge of text at desired margin from bottom
// Y = imageHeight - textHeight - bottomMargin
var desiredY1 = imgHeight - textHeight - bottomMargin1;
var desiredY4 = imgHeight - textHeight - bottomMargin4;
// Ensure text stays completely within bounds:
// - Top edge must be >= minMargin (not clipped at top)
// - Bottom edge must be <= imgHeight - minMargin (not clipped at bottom)
var maxAllowedY1 = imgHeight - textHeight - minMargin; // Maximum Y to keep bottom margin
var maxAllowedY4 = imgHeight - textHeight - minMargin;
imgState.YPosFromBottom1 = Math.Max(minMargin, Math.Min(desiredY1, maxAllowedY1));
imgState.YPosFromBottom4 = Math.Max(minMargin, Math.Min(desiredY4, maxAllowedY4));
break;