SRP code improvements

This commit is contained in:
DESKTOP\Administrator
2026-05-17 03:04:46 +03:00
parent 43ebb4c7fa
commit 09e93acece
11 changed files with 157 additions and 271 deletions

View File

@@ -2,7 +2,8 @@ using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Watermark.Net.src.WatermarkNet.Core;
using Watermark.Net.src.WatermarkNet.Types;
using Watermark.Net.src.WatermarkNet.Enums;
using Watermark.Net.src.WatermarkNet.Models.Definitions;
namespace UnitTest
{
@@ -13,7 +14,6 @@ namespace UnitTest
public void TextWatermarkTest()
{
var watermarker = new Watermarker();
var watermark = new TextWatermark();
var availableFont = SystemFonts.Families.FirstOrDefault();
if (availableFont == default)
@@ -21,11 +21,13 @@ namespace UnitTest
throw new Exception("No available fonts found in the system");
}
watermark.Text = "Test";
watermark.Color = Color.White;
watermark.Font = availableFont.CreateFont(1);
watermark.Position = Watermark.Net.src.WatermarkNet.Enums.ImagePosition.BottomCenter;
watermark.RotateAngle = 90;
var watermark = new TextWatermark{
Font = availableFont.CreateFont(1),
Text = "Test",
Style = { Color = Color.White },
Layout = { Position = ImagePosition.BottomCenter , RotateAngle = 90 }
};
var resultedImage = watermarker.ProcessImage("TestImages/2.png", "test/text", watermark);
Assert.IsTrue(File.Exists(resultedImage.Path));
@@ -36,11 +38,12 @@ namespace UnitTest
public void ImageWatermarkTest()
{
var watermarker = new Watermarker();
var watermark = new ImageWatermark();
var watermark = new ImageWatermark {
ImagePath = "TestImages/sample_wm.png",
Layout = { Position = ImagePosition.Center, Scale = 1 },
Style = { Opacity = 1 }
};
watermark.ImagePath = "TestImages/sample_wm.png";
watermark.Position = Watermark.Net.src.WatermarkNet.Enums.ImagePosition.Center;
watermark.Scale = 1;
var resultedImage = watermarker.ProcessImage("TestImages/2.png", "test/image", watermark);
Assert.IsTrue(File.Exists(resultedImage.Path));
@@ -51,7 +54,6 @@ namespace UnitTest
public void TextWatermarkDirectoryProccessTest()
{
var watermarker = new Watermarker("test/text/pave");
var watermark = new TextWatermark();
var availableFont = SystemFonts.Families.FirstOrDefault();
if (availableFont == default)
@@ -59,12 +61,13 @@ namespace UnitTest
throw new Exception("No available fonts found in the system");
}
watermark.Text = "Test";
watermark.Color = Rgba32.ParseHex("FFFFFF50");
watermark.Font = availableFont.CreateFont(1);
watermark.Scale = 1f;
watermark.Position = Watermark.Net.src.WatermarkNet.Enums.ImagePosition.TopLeft;
watermark.Pave = true;
var watermark = new TextWatermark {
Text = "Test",
Font = availableFont.CreateFont(1),
Style = { Color = Rgba32.ParseHex("FFFFFF50"), Pave = true },
Layout = { Scale = 1f, Position = ImagePosition.TopLeft }
};
watermarker.ProcessDirectory("TestImages", watermark);
Assert.IsTrue(Directory.GetFiles(watermarker.OutputDir)?.Length > 0);
@@ -74,12 +77,12 @@ namespace UnitTest
public void ImageWatermarkDirectoryProccessTest()
{
var watermarker = new Watermarker("test/image/pave");
var watermark = new ImageWatermark();
var watermark = new ImageWatermark {
ImagePath = "TestImages/sample_wm.png",
Layout = { Position = ImagePosition.Center, Scale = 1},
Style = { Pave = true, Opacity = 1}
};
watermark.ImagePath = "TestImages/sample_wm.png";
watermark.Position = Watermark.Net.src.WatermarkNet.Enums.ImagePosition.Center;
watermark.Scale = 1;
watermark.Pave = true;
watermarker.ProcessDirectory("TestImages", watermark);
Assert.IsTrue(Directory.GetFiles(watermarker.OutputDir)?.Length > 0);

View File

@@ -1,14 +1,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33213.308
# Visual Studio Version 18
VisualStudioVersion = 18.5.11709.299 stable
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Watermark.Net", "Watermark.Net.csproj", "{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "..\UnitTest\UnitTest.csproj", "{26E6D8A0-CFB1-4A30-A4DD-D6DBBDADB388}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Watrmark.Net CLI", "..\Watrmark.Net CLI\Watrmark.Net CLI.csproj", "{EC1F992E-6B68-45E7-8879-8576F728A6F3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -23,10 +21,6 @@ Global
{26E6D8A0-CFB1-4A30-A4DD-D6DBBDADB388}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26E6D8A0-CFB1-4A30-A4DD-D6DBBDADB388}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26E6D8A0-CFB1-4A30-A4DD-D6DBBDADB388}.Release|Any CPU.Build.0 = Release|Any CPU
{EC1F992E-6B68-45E7-8879-8576F728A6F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC1F992E-6B68-45E7-8879-8576F728A6F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC1F992E-6B68-45E7-8879-8576F728A6F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC1F992E-6B68-45E7-8879-8576F728A6F3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Watermark.Net.src.WatermarkNet.Enums;
using Watermark.Net.src.WatermarkNet.Models.Definitions;
using Watermark.Net.src.WatermarkNet.Types;
using static System.Net.Mime.MediaTypeNames;
using static System.Runtime.InteropServices.JavaScript.JSType;
@@ -36,7 +37,7 @@ namespace Watermark.Net.src.WatermarkNet.Core
/// <param name="watermark">Watermark configuration.</param>
/// <returns>List of processed images with watermark information.</returns>
public List<WmarkedImage> ProcessDirectory<T>(string directory, T watermark)
where T : WatermarkImageBase
where T : IWatermarkDefinition
{
List<WmarkedImage> processedImages = new List<WmarkedImage>();
foreach (var imageFile in Directory.GetFiles(directory))
@@ -80,8 +81,8 @@ namespace Watermark.Net.src.WatermarkNet.Core
using (var targetImage = Image.Load(imagePath))
using (var watermarkImage = Image.Load(watermark.ImagePath))
{
var scaledWmWidth = (int)Math.Round(watermarkImage.Width * watermark.Scale);
var scaledWmHeight = (int)Math.Round(watermarkImage.Height * watermark.Scale);
var scaledWmWidth = (int)Math.Round(watermarkImage.Width * watermark.Layout.Scale);
var scaledWmHeight = (int)Math.Round(watermarkImage.Height * watermark.Layout.Scale);
watermarkImage.Mutate(x => x.Resize(new Size(scaledWmWidth, scaledWmHeight)));
using (var markedImage = targetImage.Clone(ctx => this.ApplyScalingWaterMarkImage(ctx, watermark, watermarkImage, targetImage)))
@@ -255,32 +256,32 @@ namespace Watermark.Net.src.WatermarkNet.Core
float scalingFactor = Math.Min(imgSize.Width / size.Width, imgSize.Height / size.Height);
// Create a new font
SixLabors.Fonts.Font scaledFont = new SixLabors.Fonts.Font(watermark.Font, scalingFactor / 16 * (watermark.Font.Size * watermark.Scale));
SixLabors.Fonts.Font scaledFont = new SixLabors.Fonts.Font(watermark.Font, scalingFactor / 16 * (watermark.Font.Size * watermark.Layout.Scale));
//processingContext.SetGraphicsOptions(new GraphicsOptions { AlphaCompositionMode = SixLabors.ImageSharp.PixelFormats.PixelAlphaCompositionMode.Clear});
//If set, apply backround color
if (watermark.BackroundColor != null)
processingContext.BackgroundColor((Color)watermark.BackroundColor);
if (watermark.Style.Color != null)
processingContext.BackgroundColor((Color)watermark.Style.Color);
var textOptions = new RichTextOptions(scaledFont)
{
ColorFontSupport = ColorFontSupport.MicrosoftColrFormat,
Origin = CalcWatermarkOrigin(imgSize.Width, imgSize.Height, scaledFont.Size, watermark.Position),
HorizontalAlignment = HorizontalAlignmentFromPosition(watermark.Position),
Origin = CalcWatermarkOrigin(imgSize.Width, imgSize.Height, scaledFont.Size, watermark.Layout.Position),
HorizontalAlignment = HorizontalAlignmentFromPosition(watermark.Layout.Position),
VerticalAlignment = VerticalAlignment.Top,
};
if (watermark.Pave)
if (watermark.Style.Pave)
{
foreach (ImagePosition position in (ImagePosition[])Enum.GetValues(typeof(ImagePosition)))
{
textOptions.Origin = CalcWatermarkOrigin(imgSize.Width, imgSize.Height, scaledFont.Size, position);
textOptions.HorizontalAlignment = HorizontalAlignmentFromPosition(position);
processingContext.DrawText(textOptions, watermark.Text, watermark.Color);
processingContext.DrawText(textOptions, watermark.Text, watermark.Style.Color);
}
return processingContext;
}
return processingContext
.DrawText(textOptions, watermark.Text, watermark.Color);
.DrawText(textOptions, watermark.Text, watermark.Style.Color);
}
/// <summary>
@@ -311,20 +312,20 @@ namespace Watermark.Net.src.WatermarkNet.Core
: watermarkImage.Height / scaleFactor;
//If set, apply backround color
if (watermark.BackroundColor != null)
processingContext.BackgroundColor((Color)watermark.BackroundColor);
if (watermark.Style.Color != null)
processingContext.BackgroundColor((Color)watermark.Style.Color);
watermarkImage.Mutate(x => x.Resize(new Size((int)scaledWmWidth, (int)scaledWmHeight)));
var wmPositionOrigin = CalcWatermarkOrigin(targetImage.Width, targetImage.Height, watermarkImage.Width, watermarkImage.Height, watermark.Position);
var wmPositionOrigin = CalcWatermarkOrigin(targetImage.Width, targetImage.Height, watermarkImage.Width, watermarkImage.Height, watermark.Layout.Position);
if (watermark.Pave)
if (watermark.Style.Pave)
{
foreach (ImagePosition position in (ImagePosition[])Enum.GetValues(typeof(ImagePosition)))
{
wmPositionOrigin = CalcWatermarkOrigin(targetImage.Width, targetImage.Height, watermarkImage.Width, watermarkImage.Height, position);
try
{
processingContext.DrawImage(watermarkImage, wmPositionOrigin, watermark.Opacity);
processingContext.DrawImage(watermarkImage, wmPositionOrigin, watermark.Style.Opacity);
}
catch (Exception ex)
{
@@ -334,7 +335,7 @@ namespace Watermark.Net.src.WatermarkNet.Core
return processingContext;
}
return processingContext.DrawImage(watermarkImage, wmPositionOrigin, watermark.Opacity);
return processingContext.DrawImage(watermarkImage, wmPositionOrigin, watermark.Style.Opacity);
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Watermark.Net.src.WatermarkNet.Models.Layout;
using Watermark.Net.src.WatermarkNet.Models.Styling;
namespace Watermark.Net.src.WatermarkNet.Models.Definitions
{
public interface IWatermarkDefinition
{
WatermarkLayout Layout { get; }
WatermarkStyle Style { get; }
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Watermark.Net.src.WatermarkNet.Models.Layout;
using Watermark.Net.src.WatermarkNet.Models.Styling;
namespace Watermark.Net.src.WatermarkNet.Models.Definitions
{
public class ImageWatermark : IWatermarkDefinition
{
public required string ImagePath { get; init; }
public WatermarkLayout Layout
{
get;
init;
} = new();
public WatermarkStyle Style
{
get;
init;
} = new();
}
}

View File

@@ -0,0 +1,26 @@
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;
using Watermark.Net.src.WatermarkNet.Models.Layout;
using Watermark.Net.src.WatermarkNet.Models.Styling;
namespace Watermark.Net.src.WatermarkNet.Models.Definitions
{
public class TextWatermark : IWatermarkDefinition
{
public required string Text { get; set; }
public required Font Font { get; set; }
public WatermarkLayout Layout { get; init; }
= new();
public WatermarkStyle Style { get; init; }
= new();
}
}

View File

@@ -0,0 +1,20 @@
using Watermark.Net.src.WatermarkNet.Enums;
namespace Watermark.Net.src.WatermarkNet.Models.Layout
{
public class WatermarkLayout
{
public ImagePosition Position { get; set; }
public float Scale { get; set; }
public int RotateAngle { get; set; }
/// <summary>
/// Gets or sets the padding space around the watermark in pixels.
/// Default: 10px
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when value is negative.</exception>
public float Padding { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using SixLabors.Fonts;
using SixLabors.ImageSharp;
namespace Watermark.Net.src.WatermarkNet.Models.Styling
{
public class WatermarkStyle
{
/// <summary>
/// Gets or sets the transparency level of the watermark.
/// Range: 0.0 (fully transparent) to 1.0 (completely opaque).
/// Default: 1.0
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when value is outside the 0.0-1.0 range.
/// </exception>
public float Opacity { get; set; }
public bool Pave { get; set; }
public Color Color { get; set; }
}
}

View File

@@ -1,75 +0,0 @@
using System;
using System.IO;
namespace Watermark.Net.src.WatermarkNet.Types
{
/// <summary>
/// Represents a configurable image-based watermark with scaling, positioning, and transparency controls.
/// </summary>
public class ImageWatermark : WatermarkImageBase
{
private string _imagePath;
private float? _opacity;
/// <summary>
/// Gets or sets the absolute path to the watermark image file.
/// Supported formats: PNG, JPEG, BMP, GIF.
/// </summary>
/// <exception cref="FileNotFoundException">
/// Thrown when setting a value that doesn't point to an existing file.
/// </exception>
public string ImagePath
{
get => _imagePath;
set
{
if (!File.Exists(value))
throw new FileNotFoundException("Watermark image not found", value);
_imagePath = value;
}
}
/// <summary>
/// Gets or sets the transparency level of the watermark.
/// Range: 0.0 (fully transparent) to 1.0 (completely opaque).
/// Default: 1.0
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when value is outside the 0.0-1.0 range.
/// </exception>
public float Opacity
{
get => _opacity ?? 1f;
set
{
if (value < 0 || value > 1)
throw new ArgumentOutOfRangeException(nameof(Opacity),
"Opacity must be between 0.0 and 1.0");
_opacity = value;
}
}
/// <summary>
/// Gets or sets the scale factor applied to the watermark image relative to the target image.
/// Default: 0.2 (20% of target image width)
/// </summary>
public float Scale { get; set; } = 0.2f;
/// <summary>
/// Initializes a new instance of the ImageWatermark class with specified image path.
/// </summary>
/// <param name="imagePath">Path to the watermark image file.</param>
public ImageWatermark(string imagePath) : this()
{
ImagePath = imagePath;
}
/// <summary>
/// Initializes a new instance of the ImageWatermark class with default values.
/// </summary>
public ImageWatermark() { }
}
}

View File

@@ -1,88 +0,0 @@
using SixLabors.Fonts;
using SixLabors.ImageSharp;
namespace Watermark.Net.src.WatermarkNet.Types
{
/// <summary>
/// Represents a configurable text-based watermark with styling, positioning, and rendering options.
/// </summary>
public class TextWatermark : WatermarkImageBase
{
private string _text;
private Font _font;
private float _padding;
private Color _color;
/// <summary>
/// Gets or sets the color of the text watermark.
/// Default: White (#FFFFFF)
/// </summary>
public Color Color
{
get => _color;
set => _color = value;
}
/// <summary>
/// Gets or sets the text content for the watermark.
/// </summary>
/// <exception cref="ArgumentException">Thrown when value is null or whitespace.</exception>
public string Text
{
get => _text;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Watermark text cannot be empty", nameof(Text));
_text = value;
}
}
/// <summary>
/// Gets or sets the font used to render the text watermark.
/// Default: Arial 12pt
/// </summary>
/// <exception cref="ArgumentNullException">Thrown when value is null.</exception>
public Font Font
{
get => _font;
set => _font = value ?? throw new ArgumentNullException(nameof(Font));
}
/// <summary>
/// Gets or sets the padding space around the text watermark in pixels.
/// Default: 10px
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when value is negative.</exception>
public float Padding
{
get => _padding;
set
{
if (value < 0) throw new ArgumentOutOfRangeException(nameof(Padding), "Padding cannot be negative");
_padding = value;
}
}
/// <summary>
/// Gets or sets the rotation angle for the text watermark in degrees.
/// Default: 0 (no rotation)
/// </summary>
public float Rotation { get; set; } = 0;
/// <summary>
/// Initializes a new instance of the TextWatermark class with default values.
/// </summary>
public TextWatermark()
{
var availableFont = SystemFonts.Families.FirstOrDefault();
if (availableFont == default)
{
throw new Exception("No available fonts found in the system");
}
_color = Color.White;
_font = availableFont.CreateFont(1);
_padding = 10f;
}
}
}

View File

@@ -1,60 +0,0 @@
using SixLabors.ImageSharp;
using Watermark.Net.src.WatermarkNet.Enums;
namespace Watermark.Net.src.WatermarkNet.Types
{
public class WatermarkImageBase
{
protected ImagePosition _postion;
protected float _scale;
protected int _rotateAngle;
protected bool _pave;
protected Color? _backround;
public ImagePosition Position
{
get { return _postion; }
set
{
_postion= value;
}
}
public float Scale
{
get { return _scale; }
set
{
if(value <= 0) { throw new ArgumentOutOfRangeException("Scale", "Image scale can not be less or equal zero."); }
_scale= value;
}
}
public int RotateAngle
{
get { return _rotateAngle; }
set
{
if (Math.Abs(value) > 180) { throw new ArgumentOutOfRangeException("RotateAngle", "Image rotate angle can not be larger than 180°."); }
_rotateAngle= value;
}
}
public bool Pave
{
get { return _pave; }
set { _pave = value; }
}
public Color? BackroundColor
{
get { return _backround; }
set { _backround = value; }
}
public WatermarkImageBase()
{
_scale= 1.0f;
}
}
}