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:
parent
90fb03bf0c
commit
d62342aae1
11 changed files with 455 additions and 74 deletions
44
MaddoShared.ImageSharpTests/Helpers/CreatorFactory.cs
Normal file
44
MaddoShared.ImageSharpTests/Helpers/CreatorFactory.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System.IO;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Helpers
|
||||
{
|
||||
public static class CreatorFactory
|
||||
{
|
||||
public static MaddoShared.PicSettings CreateDefaultPicSettings()
|
||||
{
|
||||
return new MaddoShared.PicSettings
|
||||
{
|
||||
DimStandard = 48,
|
||||
DimStandardMiniatura = 12,
|
||||
LarghezzaSmall = 150,
|
||||
AltezzaSmall = 150,
|
||||
LarghezzaBig = 800,
|
||||
AltezzaBig = 600,
|
||||
Trasparenza = 0,
|
||||
IlFont = "Arial",
|
||||
Grassetto = false,
|
||||
Posizione = "CENTRO",
|
||||
Allineamento = "CENTRO",
|
||||
Margine = 10,
|
||||
MargVert = 10,
|
||||
TestoMin = false,
|
||||
AggNumTempMin = false,
|
||||
CreaMiniature = false,
|
||||
LogoAggiungi = false,
|
||||
LogoAltezza = 100,
|
||||
LogoLarghezza = 100,
|
||||
LogoMargine = "0",
|
||||
JpegQuality = 90,
|
||||
JpegQualityMin = 75,
|
||||
};
|
||||
}
|
||||
|
||||
public static MaddoShared.ImageCreatorImageSharp CreateImageCreator(MaddoShared.PicSettings settings)
|
||||
{
|
||||
var logger = NullLogger<MaddoShared.ImageCreatorImageSharp>.Instance;
|
||||
return new MaddoShared.ImageCreatorImageSharp(settings, logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
MaddoShared.ImageSharpTests/Helpers/PixelInspector.cs
Normal file
40
MaddoShared.ImageSharpTests/Helpers/PixelInspector.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Helpers
|
||||
{
|
||||
public static class PixelInspector
|
||||
{
|
||||
public static int CountNonBackgroundPixels(string path, int x, int y, int width, int height, Rgba32 background, int tolerance = 0)
|
||||
{
|
||||
using var img = SixLabors.ImageSharp.Image.Load<Rgba32>(path);
|
||||
var bx = Math.Max(0, x);
|
||||
var by = Math.Max(0, y);
|
||||
var bw = Math.Min(width, img.Width - bx);
|
||||
var bh = Math.Min(height, img.Height - by);
|
||||
if (bw <= 0 || bh <= 0) return 0;
|
||||
|
||||
int count = 0;
|
||||
img.ProcessPixelRows(accessor =>
|
||||
{
|
||||
for (int yy = by; yy < by + bh; yy++)
|
||||
{
|
||||
var row = accessor.GetRowSpan(yy);
|
||||
for (int xx = bx; xx < bx + bw; xx++)
|
||||
{
|
||||
var p = row[xx];
|
||||
if (!IsApproximatelyEqual(p, background, tolerance)) count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static bool IsApproximatelyEqual(Rgba32 a, Rgba32 b, int tol)
|
||||
{
|
||||
return Math.Abs(a.R - b.R) <= tol && Math.Abs(a.G - b.G) <= tol && Math.Abs(a.B - b.B) <= tol && Math.Abs(a.A - b.A) <= tol;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
MaddoShared.ImageSharpTests/Helpers/TempWorkspace.cs
Normal file
33
MaddoShared.ImageSharpTests/Helpers/TempWorkspace.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Helpers
|
||||
{
|
||||
public sealed class TempWorkspace : IDisposable
|
||||
{
|
||||
public DirectoryInfo Root { get; }
|
||||
public DirectoryInfo SourceDir { get; }
|
||||
public DirectoryInfo DestDir { get; }
|
||||
|
||||
public TempWorkspace()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "MaddoShared.ImageSharpTests", Guid.NewGuid().ToString("N"));
|
||||
Root = Directory.CreateDirectory(root);
|
||||
SourceDir = Directory.CreateDirectory(Path.Combine(Root.FullName, "Source"));
|
||||
DestDir = Directory.CreateDirectory(Path.Combine(Root.FullName, "Dest"));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Root.Exists)
|
||||
Root.Delete(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// best-effort cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
MaddoShared.ImageSharpTests/Helpers/TestImageFactory.cs
Normal file
45
MaddoShared.ImageSharpTests/Helpers/TestImageFactory.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System.IO;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Helpers
|
||||
{
|
||||
public static class TestImageFactory
|
||||
{
|
||||
public static string CreateSolidJpeg(string directory, string fileName, int width, int height, Rgba32 color)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, fileName);
|
||||
using var img = new Image<Rgba32>(width, height, color);
|
||||
var encoder = new JpegEncoder { Quality = 90 };
|
||||
img.Save(path, encoder);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string CreateSolidPng(string directory, string fileName, int width, int height, Rgba32 color)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, fileName);
|
||||
using var img = new Image<Rgba32>(width, height, color);
|
||||
img.SaveAsPng(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string CreateJpegWithExifOrientation(string directory, string fileName, int width, int height, Rgba32 color, ushort orientation)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, fileName);
|
||||
using var img = new Image<Rgba32>(width, height, color);
|
||||
// Add EXIF orientation
|
||||
var profile = new ExifProfile();
|
||||
profile.SetValue(ExifTag.Orientation, orientation);
|
||||
img.Metadata.ExifProfile = profile;
|
||||
var encoder = new JpegEncoder { Quality = 90 };
|
||||
img.Save(path, encoder);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.8.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
<PackageReference Include="SixLabors.Fonts" Version="2.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
43
MaddoShared.ImageSharpTests/Tests/ImageResizingTests.cs
Normal file
43
MaddoShared.ImageSharpTests/Tests/ImageResizingTests.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using MaddoShared.ImageSharpTests.Helpers;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ImageResizingTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task BigImageResizesRespectSettings()
|
||||
{
|
||||
using var ws = new TempWorkspace();
|
||||
|
||||
// create a large input image
|
||||
var inputPath = TestImageFactory.CreateSolidJpeg(ws.SourceDir.FullName, "input.jpg", 1600, 1200, new Rgba32(200, 200, 200, 255));
|
||||
|
||||
var pic = CreatorFactory.CreateDefaultPicSettings();
|
||||
pic.LarghezzaBig = 800;
|
||||
pic.AltezzaBig = 600;
|
||||
pic.CreaMiniature = false;
|
||||
|
||||
var svc = CreatorFactory.CreateImageCreator(pic);
|
||||
|
||||
var state = new MaddoShared.ImageState
|
||||
{
|
||||
WorkFile = new FileInfo(inputPath),
|
||||
DestDir = ws.DestDir,
|
||||
SourceDir = ws.SourceDir
|
||||
};
|
||||
|
||||
await svc.CreateImageAsync(state, null);
|
||||
|
||||
var outPath = Path.Combine(ws.DestDir.FullName, state.NomeFileBig);
|
||||
using var outImg = SixLabors.ImageSharp.Image.Load<Rgba32>(outPath);
|
||||
|
||||
Assert.AreEqual(800, outImg.Width);
|
||||
Assert.AreEqual(600, outImg.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
MaddoShared.ImageSharpTests/Tests/TextPositioningTests.cs
Normal file
50
MaddoShared.ImageSharpTests/Tests/TextPositioningTests.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using MaddoShared.ImageSharpTests.Helpers;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class TextPositioningTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task TextAtBottom_IncreasesNonBackgroundPixelCountInBottomBand()
|
||||
{
|
||||
using var ws = new TempWorkspace();
|
||||
|
||||
// create white background input
|
||||
var inputPath = TestImageFactory.CreateSolidJpeg(ws.SourceDir.FullName, "input.jpg", 800, 600, new Rgba32(255, 255, 255, 255));
|
||||
|
||||
var pic = CreatorFactory.CreateDefaultPicSettings();
|
||||
pic.Posizione = "BASSO";
|
||||
pic.DimStandard = 48; // big text
|
||||
pic.TestoFirmaStart = "SAMPLE TEXT";
|
||||
pic.CreaMiniature = false;
|
||||
|
||||
var svc = CreatorFactory.CreateImageCreator(pic);
|
||||
|
||||
var state = new MaddoShared.ImageState
|
||||
{
|
||||
WorkFile = new FileInfo(inputPath),
|
||||
DestDir = ws.DestDir,
|
||||
SourceDir = ws.SourceDir
|
||||
};
|
||||
|
||||
await svc.CreateImageAsync(state, null);
|
||||
|
||||
var outPath = Path.Combine(ws.DestDir.FullName, state.NomeFileBig);
|
||||
|
||||
// bottom band (lower 25% of image)
|
||||
var bottomY = (int)(600 * 0.75);
|
||||
var bottomCount = PixelInspector.CountNonBackgroundPixels(outPath, 0, bottomY, 800, 600 - bottomY, new Rgba32(255, 255, 255, 255), tolerance: 10);
|
||||
|
||||
// top band (upper 25%)
|
||||
var topCount = PixelInspector.CountNonBackgroundPixels(outPath, 0, 0, 800, (int)(600 * 0.25), new Rgba32(255, 255, 255, 255), tolerance: 10);
|
||||
|
||||
Assert.IsTrue(bottomCount > 50, $"Expected text pixels in bottom band, found {bottomCount}");
|
||||
Assert.IsTrue(bottomCount > topCount, "Expected more non-background pixels at bottom than top");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue