diff --git a/.gitignore b/.gitignore
index ed5c47e..518a7ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,4 +53,6 @@ Watermark.Net/.vs
# NUnit
*.VisualState.xml
TestResult.xml
-nunit-*.xml
\ No newline at end of file
+nunit-*.xml
+
+Watermark.Net/.vs
\ No newline at end of file
diff --git a/UnitTest/UnitTest.cs b/UnitTest/UnitTest.cs
index 68fac3e..31e0c7d 100644
--- a/UnitTest/UnitTest.cs
+++ b/UnitTest/UnitTest.cs
@@ -11,7 +11,7 @@ namespace UnitTest
public class UnitTest
{
[TestMethod]
- public void TextWatermarkTest()
+ public void TextWatermarkLegacyTest()
{
var watermarker = new Watermarker();
@@ -30,12 +30,12 @@ namespace UnitTest
var resultedImage = watermarker.ProcessImage("TestImages/2.png", "test/text", watermark);
- Assert.IsTrue(File.Exists(resultedImage.Path));
Assert.IsNotNull(resultedImage);
+ Assert.IsTrue(File.Exists(resultedImage.Path));
}
[TestMethod]
- public void ImageWatermarkTest()
+ public void ImageWatermarkLegacyTest()
{
var watermarker = new Watermarker();
var watermark = new ImageWatermark {
@@ -46,12 +46,12 @@ namespace UnitTest
var resultedImage = watermarker.ProcessImage("TestImages/2.png", "test/image", watermark);
- Assert.IsTrue(File.Exists(resultedImage.Path));
Assert.IsNotNull(resultedImage);
+ Assert.IsTrue(File.Exists(resultedImage.Path));
}
[TestMethod]
- public void TextWatermarkDirectoryProccessTest()
+ public void TextWatermarkDirectoryProccessLegacyTest()
{
var watermarker = new Watermarker("test/text/pave");
@@ -74,7 +74,7 @@ namespace UnitTest
}
[TestMethod]
- public void ImageWatermarkDirectoryProccessTest()
+ public void ImageWatermarkDirectoryProccessLegacyTest()
{
var watermarker = new Watermarker("test/image/pave");
var watermark = new ImageWatermark {
@@ -87,5 +87,93 @@ namespace UnitTest
Assert.IsTrue(Directory.GetFiles(watermarker.OutputDir)?.Length > 0);
}
+
+ [TestMethod]
+ public void TextWatermarkPipelineTest()
+ {
+ var fileManager = new FileManager();
+ var renderer = new ImageRenderer();
+ var pipeline = new WatermarkPipeline(fileManager, renderer);
+
+ var availableFont = SystemFonts.Families.FirstOrDefault();
+ if (availableFont == default)
+ {
+ throw new Exception("No available fonts found in the system");
+ }
+
+ var watermark = new TextWatermark{
+ Font = availableFont.CreateFont(1),
+ Text = "Test",
+ Style = { Color = Color.White },
+ Layout = { Position = ImagePosition.BottomCenter , RotateAngle = 90 }
+ };
+
+ var resultedImage = pipeline.ProcessImage("TestImages/2.png", "test/pipeline/text", watermark);
+
+ Assert.IsNotNull(resultedImage);
+ Assert.IsTrue(File.Exists(resultedImage.Path));
+ }
+
+ [TestMethod]
+ public void ImageWatermarkPipelineTest()
+ {
+ var fileManager = new FileManager();
+ var renderer = new ImageRenderer();
+ var pipeline = new WatermarkPipeline(fileManager, renderer);
+
+ var watermark = new ImageWatermark {
+ ImagePath = "TestImages/sample_wm.png",
+ Layout = { Position = ImagePosition.Center, Scale = 1 },
+ Style = { Opacity = 1 }
+ };
+
+ var resultedImage = pipeline.ProcessImage("TestImages/2.png", "test/pipeline/image", watermark);
+
+ Assert.IsNotNull(resultedImage);
+ Assert.IsTrue(File.Exists(resultedImage.Path));
+ }
+
+ [TestMethod]
+ public void TextWatermarkDirectoryPipelineTest()
+ {
+ var fileManager = new FileManager();
+ var renderer = new ImageRenderer();
+ var pipeline = new WatermarkPipeline(fileManager, renderer);
+
+ var availableFont = SystemFonts.Families.FirstOrDefault();
+ if (availableFont == default)
+ {
+ throw new Exception("No available fonts found in the system");
+ }
+
+ var watermark = new TextWatermark {
+ Text = "Test",
+ Font = availableFont.CreateFont(1),
+ Style = { Color = Rgba32.ParseHex("FFFFFF50"), Pave = true },
+ Layout = { Scale = 1f, Position = ImagePosition.TopLeft }
+ };
+
+ var results = pipeline.ProcessDirectory("TestImages", "test/pipeline/text/dir", watermark);
+
+ Assert.IsTrue(results.Count > 0);
+ }
+
+ [TestMethod]
+ public void ImageWatermarkDirectoryPipelineTest()
+ {
+ var fileManager = new FileManager();
+ var renderer = new ImageRenderer();
+ var pipeline = new WatermarkPipeline(fileManager, renderer);
+
+ var watermark = new ImageWatermark {
+ ImagePath = "TestImages/sample_wm.png",
+ Layout = { Position = ImagePosition.Center, Scale = 1},
+ Style = { Pave = true, Opacity = 1}
+ };
+
+ var results = pipeline.ProcessDirectory("TestImages", "test/pipeline/image/dir", watermark);
+
+ Assert.IsTrue(results.Count > 0);
+ }
}
}
diff --git a/Watermark.Net/src/WatermarkNet.Common/FileManager.cs b/Watermark.Net/src/WatermarkNet.Common/FileManager.cs
new file mode 100644
index 0000000..1fa507a
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Common/FileManager.cs
@@ -0,0 +1,63 @@
+using SixLabors.ImageSharp;
+
+namespace Watermark.Net.src.WatermarkNet.Core
+{
+ ///
+ /// Default implementation of .
+ /// Encapsulates all file system operations (loading, saving, validation, enumeration).
+ ///
+ public class FileManager : IFileManager
+ {
+ ///
+ public Image LoadImage(string path)
+ {
+ return Image.Load(path);
+ }
+
+ ///
+ public void SaveImage(Image image, string path)
+ {
+ image.Save(path);
+ }
+
+ ///
+ public IEnumerable EnumerateFiles(string directory)
+ {
+ foreach (var filePath in Directory.GetFiles(directory))
+ {
+ var attributes = File.GetAttributes(filePath);
+ bool isDirectory = attributes.HasFlag(FileAttributes.Directory);
+ bool isHidden = attributes.HasFlag(FileAttributes.Hidden);
+
+ if (!isDirectory && !isHidden)
+ {
+ yield return filePath;
+ }
+ }
+ }
+
+ ///
+ public void EnsureDirectoryExists(string path)
+ {
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+ }
+
+ ///
+ public void ValidateFileExists(string path)
+ {
+ if (!File.Exists(path))
+ {
+ throw new FileNotFoundException("Source file not found!", path);
+ }
+ }
+
+ ///
+ public string CombinePath(string directory, string fileName)
+ {
+ return directory + Path.DirectorySeparatorChar + fileName;
+ }
+ }
+}
diff --git a/Watermark.Net/src/WatermarkNet.Common/IFileManager.cs b/Watermark.Net/src/WatermarkNet.Common/IFileManager.cs
new file mode 100644
index 0000000..4109ab1
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Common/IFileManager.cs
@@ -0,0 +1,41 @@
+using SixLabors.ImageSharp;
+
+namespace Watermark.Net.src.WatermarkNet.Core
+{
+ ///
+ /// Abstraction for file system operations
+ ///
+ public interface IFileManager
+ {
+ ///
+ /// Loads an image from the specified path.
+ ///
+ Image LoadImage(string path);
+
+ ///
+ /// Saves an image to the specified path.
+ ///
+ void SaveImage(Image image, string path);
+
+ ///
+ /// Enumerates all files in a directory (non-recursive, top-level only).
+ /// Skips directories and hidden files.
+ ///
+ IEnumerable EnumerateFiles(string directory);
+
+ ///
+ /// Creates the directory if it does not exist.
+ ///
+ void EnsureDirectoryExists(string path);
+
+ ///
+ /// Throws if the file does not exist.
+ ///
+ void ValidateFileExists(string path);
+
+ ///
+ /// Combines a directory path with a file name using the platform separator.
+ ///
+ string CombinePath(string directory, string fileName);
+ }
+}
diff --git a/Watermark.Net/src/WatermarkNet.Common/ImageRenderer.cs b/Watermark.Net/src/WatermarkNet.Common/ImageRenderer.cs
new file mode 100644
index 0000000..c1befdf
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Common/ImageRenderer.cs
@@ -0,0 +1,237 @@
+using SixLabors.Fonts;
+using SixLabors.ImageSharp;
+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;
+
+namespace Watermark.Net.src.WatermarkNet.Core
+{
+ ///
+ /// Pure rendering engine for watermark operations.
+ /// Contains only image processing logic — no file I/O, no directory traversal.
+ /// Accepts in-memory objects and returns processed instances.
+ ///
+ public class ImageRenderer
+ {
+ ///
+ /// Applies a text watermark to the target image.
+ ///
+ /// Source image to watermark.
+ /// Text watermark configuration.
+ /// A new image instance with the watermark applied.
+ public Image ApplyTextWatermark(Image targetImage, TextWatermark watermark)
+ {
+ return targetImage.Clone(ctx => ApplyScalingWaterMarkText(ctx, watermark));
+ }
+
+ ///
+ /// Applies an image watermark to the target image.
+ ///
+ /// Source image to watermark.
+ /// The watermark image (already loaded).
+ /// Image watermark configuration.
+ /// A new image instance with the watermark applied.
+ public Image ApplyImageWatermark(Image targetImage, Image watermarkImage, ImageWatermark watermark)
+ {
+ return targetImage.Clone(ctx => ApplyScalingWaterMarkImage(ctx, watermark, watermarkImage, targetImage));
+ }
+
+ ///
+ /// Calculates origin point for text watermark based on position and size.
+ ///
+ private PointF CalcWatermarkOrigin(int width, int height, float watermarkSize, ImagePosition position)
+ {
+ var origin = new PointF(0, 0);
+ //Static value 1 pt is 72 px per inch
+ var pixelsPerInch = 72;
+ var wmHeight = watermarkSize / pixelsPerInch;
+
+ switch (position)
+ {
+ case ImagePosition.TopLeft:
+ origin = new PointF(watermarkSize / 2, wmHeight / 2);
+ break;
+ case ImagePosition.TopCenter:
+ origin = new PointF(width / 2, wmHeight / 2);
+ break;
+ case ImagePosition.TopRight:
+ origin = new PointF(width - watermarkSize * 2, wmHeight / 2);
+ break;
+ case ImagePosition.CenterLeft:
+ origin = new PointF(watermarkSize / 2, height / 2.5f);
+ break;
+ case ImagePosition.Center:
+ origin = new PointF(width / 2, height / 2.5f);
+ break;
+ case ImagePosition.CenterRight:
+ origin = new PointF(width - watermarkSize * 2, height / 2.5f);
+ break;
+ case ImagePosition.BottomLeft:
+ origin = new PointF(watermarkSize / 2, height - watermarkSize * 2);
+ break;
+ case ImagePosition.BottomCenter:
+ origin = new PointF(width / 2, height - watermarkSize * 2);
+ break;
+ case ImagePosition.BottomRight:
+ origin = new PointF(width - watermarkSize * 2, height - watermarkSize * 2);
+ break;
+ default:
+ break;
+ }
+ return origin;
+ }
+
+ ///
+ /// Calculates origin point for image watermark based on position and dimensions.
+ ///
+ private Point CalcWatermarkOrigin(int width, int height, int wmWidth, int wmHeight, ImagePosition position)
+ {
+ var origin = new Point(0, 0);
+
+ var wmPaddingX = width - wmWidth;
+ var paddingSide = wmPaddingX / 2;
+
+ switch (position)
+ {
+ case ImagePosition.TopLeft:
+ origin = new Point(wmWidth / 2, wmHeight / 2);
+ break;
+ case ImagePosition.TopCenter:
+ origin = new Point((int)paddingSide, wmHeight / 2);
+ break;
+ case ImagePosition.TopRight:
+ origin = new Point(width - wmWidth * 2, wmHeight / 2);
+ break;
+ case ImagePosition.CenterLeft:
+ origin = new Point(wmWidth / 2, height / 2);
+ break;
+ case ImagePosition.Center:
+ origin = new Point((int)paddingSide, (int)(height / 2));
+ break;
+ case ImagePosition.CenterRight:
+ origin = new Point(width - wmWidth * 2, height / 2);
+ break;
+ case ImagePosition.BottomLeft:
+ origin = new Point(wmWidth / 2, height - wmHeight);
+ break;
+ case ImagePosition.BottomCenter:
+ origin = new Point((int)paddingSide, height - wmHeight);
+ break;
+ case ImagePosition.BottomRight:
+ origin = new Point(width - wmWidth * 2, height - wmHeight);
+ break;
+ default:
+ break;
+ }
+ return origin;
+ }
+
+ ///
+ /// Determines horizontal alignment based on watermark position.
+ ///
+ private HorizontalAlignment HorizontalAlignmentFromPosition(ImagePosition imagePosition)
+ {
+ switch (imagePosition)
+ {
+ case ImagePosition.TopCenter:
+ return HorizontalAlignment.Center;
+ case ImagePosition.Center:
+ return HorizontalAlignment.Center;
+ case ImagePosition.BottomCenter:
+ return HorizontalAlignment.Center;
+ default:
+ return HorizontalAlignment.Center;
+ }
+ }
+
+ ///
+ /// Applies text watermark to image with automatic scaling and positioning.
+ ///
+ private IImageProcessingContext ApplyScalingWaterMarkText(IImageProcessingContext processingContext, TextWatermark watermark)
+ {
+ Size imgSize = processingContext.GetCurrentSize();
+ FontRectangle size = TextMeasurer.MeasureSize(watermark.Text, new TextOptions(watermark.Font));
+
+ // Find out how much we need to scale the text to fill the space (up or down)
+ 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.Layout.Scale));
+
+ //If set, apply backround color
+ 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.Layout.Position),
+ HorizontalAlignment = HorizontalAlignmentFromPosition(watermark.Layout.Position),
+ VerticalAlignment = VerticalAlignment.Top,
+ };
+
+ 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.Style.Color);
+ }
+ return processingContext;
+ }
+ return processingContext
+ .DrawText(textOptions, watermark.Text, watermark.Style.Color);
+ }
+
+ ///
+ /// Applies image watermark to target image with scaling and positioning.
+ ///
+ private IImageProcessingContext ApplyScalingWaterMarkImage(IImageProcessingContext processingContext, ImageWatermark watermark, Image watermarkImage, Image targetImage)
+ {
+ var scaleFactor = 1f;
+ if (targetImage.Width > targetImage.Height)
+ scaleFactor = (float)targetImage.Width / targetImage.Height;
+ else
+ scaleFactor = (float)targetImage.Height / targetImage.Width;
+
+ var wmPaddingX = (targetImage.Width - watermarkImage.Width) / 2;
+ var wmPaddingY = (targetImage.Height - watermarkImage.Height) / 2;
+ var minWmPadding = 50;
+ var scaledWmWidth = wmPaddingX > minWmPadding && wmPaddingY > minWmPadding
+ ? watermarkImage.Width * scaleFactor
+ : watermarkImage.Width / scaleFactor;
+ var scaledWmHeight = wmPaddingX > minWmPadding && wmPaddingY > minWmPadding
+ ? watermarkImage.Height * scaleFactor
+ : watermarkImage.Height / scaleFactor;
+
+ //If set, apply backround color
+ 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.Layout.Position);
+
+ 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.Style.Opacity);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.ToString());
+ }
+ }
+ return processingContext;
+ }
+
+ return processingContext.DrawImage(watermarkImage, wmPositionOrigin, watermark.Style.Opacity);
+ }
+ }
+}
diff --git a/Watermark.Net/src/WatermarkNet.Common/WatermarkPipeline.cs b/Watermark.Net/src/WatermarkNet.Common/WatermarkPipeline.cs
new file mode 100644
index 0000000..442ae58
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Common/WatermarkPipeline.cs
@@ -0,0 +1,127 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Processing;
+using Watermark.Net.src.WatermarkNet.Models.Definitions;
+
+namespace Watermark.Net.src.WatermarkNet.Core
+{
+ ///
+ /// Orchestrates the watermark processing pipeline.
+ /// Coordinates file I/O (via ) and rendering (via )
+ /// to provide high-level operations for processing single images and directories.
+ ///
+ public class WatermarkPipeline
+ {
+ private readonly IFileManager _fileManager;
+ private readonly ImageRenderer _renderer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// File system abstraction for I/O operations.
+ /// Pure rendering engine for watermark application.
+ public WatermarkPipeline(IFileManager fileManager, ImageRenderer renderer)
+ {
+ _fileManager = fileManager ?? throw new ArgumentNullException(nameof(fileManager));
+ _renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
+ }
+
+ ///
+ /// Applies an image watermark to a single image and saves the result.
+ ///
+ /// Path to the source image file.
+ /// Directory where the processed image will be saved.
+ /// Image watermark configuration.
+ /// Result information about the processed image.
+ /// Thrown when the source image or watermark image is missing.
+ public ResultImage ProcessImage(string imagePath, string outputDirectory, ImageWatermark watermark)
+ {
+ ArgumentNullException.ThrowIfNull(watermark);
+
+ _fileManager.ValidateFileExists(imagePath);
+ _fileManager.ValidateFileExists(watermark.ImagePath);
+ _fileManager.EnsureDirectoryExists(outputDirectory);
+
+ using var targetImage = _fileManager.LoadImage(imagePath);
+ using var watermarkImage = _fileManager.LoadImage(watermark.ImagePath);
+
+ 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 = _renderer.ApplyImageWatermark(targetImage, watermarkImage, watermark);
+
+ var outputPath = _fileManager.CombinePath(outputDirectory, Path.GetFileName(imagePath));
+ _fileManager.SaveImage(markedImage, outputPath);
+
+ return new ResultImage(markedImage, outputPath);
+ }
+
+ ///
+ /// Applies a text watermark to a single image and saves the result.
+ ///
+ /// Path to the source image file.
+ /// Directory where the processed image will be saved.
+ /// Text watermark configuration.
+ /// Result information about the processed image.
+ /// Thrown when the source image is missing.
+ public ResultImage ProcessImage(string imagePath, string outputDirectory, TextWatermark watermark)
+ {
+ ArgumentNullException.ThrowIfNull(watermark);
+
+ _fileManager.ValidateFileExists(imagePath);
+ _fileManager.EnsureDirectoryExists(outputDirectory);
+
+ using var targetImage = _fileManager.LoadImage(imagePath);
+ using var markedImage = _renderer.ApplyTextWatermark(targetImage, watermark);
+
+ var outputPath = _fileManager.CombinePath(outputDirectory, Path.GetFileName(imagePath));
+ _fileManager.SaveImage(markedImage, outputPath);
+
+ return new ResultImage(markedImage, outputPath);
+ }
+
+ ///
+ /// Processes all images in a directory, applying the specified image watermark to each.
+ ///
+ /// Source directory containing images to process.
+ /// Directory where processed images will be saved.
+ /// Image watermark configuration.
+ /// A list of result information for each processed image.
+ public List ProcessDirectory(string directory, string outputDirectory, ImageWatermark watermark)
+ {
+ ArgumentNullException.ThrowIfNull(watermark);
+
+ var processedImages = new List();
+
+ foreach (var imageFile in _fileManager.EnumerateFiles(directory))
+ {
+ var resultedImage = ProcessImage(imageFile, outputDirectory, watermark);
+ processedImages.Add(resultedImage);
+ }
+
+ return processedImages;
+ }
+
+ ///
+ /// Processes all images in a directory, applying the specified text watermark to each.
+ ///
+ /// Source directory containing images to process.
+ /// Directory where processed images will be saved.
+ /// Text watermark configuration.
+ /// A list of result information for each processed image.
+ public List ProcessDirectory(string directory, string outputDirectory, TextWatermark watermark)
+ {
+ ArgumentNullException.ThrowIfNull(watermark);
+
+ var processedImages = new List();
+
+ foreach (var imageFile in _fileManager.EnumerateFiles(directory))
+ {
+ var resultedImage = ProcessImage(imageFile, outputDirectory, watermark);
+ processedImages.Add(resultedImage);
+ }
+
+ return processedImages;
+ }
+ }
+}
diff --git a/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs b/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs
index 0fd5587..2b751d5 100644
--- a/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs
+++ b/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs
@@ -1,340 +1,125 @@
-using SixLabors.Fonts;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Drawing.Processing;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
-using Watermark.Net.src.WatermarkNet.Enums;
+using SixLabors.ImageSharp;
using Watermark.Net.src.WatermarkNet.Models.Definitions;
-using static System.Net.Mime.MediaTypeNames;
-using static System.Runtime.InteropServices.JavaScript.JSType;
using Image = SixLabors.ImageSharp.Image;
namespace Watermark.Net.src.WatermarkNet.Core
{
+ ///
+ /// Provides backward compatibility for consumers of the original Watermarker API.
+ /// Internally delegates all operations to ,
+ /// , and .
+ ///
+ [Obsolete("Use WatermarkPipeline with IFileManager and ImageRenderer instead. " +
+ "This facade will be removed in a future version.")]
public class Watermarker
{
+ private readonly WatermarkPipeline _pipeline;
+ private readonly IFileManager _fileManager;
+ private readonly ImageRenderer _renderer;
private string _outputDir;
///
/// Gets or sets the output directory for processed images.
+ /// Only used by overloads
+ /// that rely on the instance-level output directory.
///
- public string OutputDir { get { return _outputDir; } set { _outputDir = value; } }
- public Watermarker()
- {
-
+ public string OutputDir
+ {
+ get { return _outputDir; }
+ set { _outputDir = value; }
}
- public Watermarker(string outputDir)
+ ///
+ /// Initializes a new instance of the class
+ /// with default and .
+ ///
+ public Watermarker()
+ : this(new FileManager(), new ImageRenderer(), string.Empty)
{
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with a specified output directory and default dependencies.
+ ///
+ /// Default output directory for processed images.
+ public Watermarker(string outputDir)
+ : this(new FileManager(), new ImageRenderer(), outputDir)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// with custom dependencies (useful for testing).
+ ///
+ public Watermarker(IFileManager fileManager, ImageRenderer renderer, string outputDir)
+ {
+ _fileManager = fileManager ?? new FileManager();
+ _renderer = renderer ?? new ImageRenderer();
+ _pipeline = new WatermarkPipeline(_fileManager, _renderer);
_outputDir = outputDir;
}
///
/// Processes all images in a directory applying watermark.
+ /// Uses the instance-level as the output directory.
///
- /// Source directory containing images to process.
- /// Watermark configuration.
- /// List of processed images with watermark information.
+ [Obsolete("Use WatermarkPipeline.ProcessDirectory instead.")]
public List ProcessDirectory(string directory, T watermark)
where T : IWatermarkDefinition
{
List processedImages = new List();
- foreach (var imageFile in Directory.GetFiles(directory))
- {
- //Do not process directories and hidden files
- if (File.GetAttributes(imageFile).HasFlag(FileAttributes.Directory) || File.GetAttributes(imageFile).HasFlag(FileAttributes.Hidden))
- continue;
+ foreach (var imageFile in _fileManager.EnumerateFiles(directory))
+ {
ResultImage? resultedImage = null;
- if (typeof(T).IsAssignableTo(typeof(ImageWatermark)))
+
+ if (watermark is ImageWatermark imageWm)
{
- var concreateWatermark = (ImageWatermark)Convert.ChangeType(watermark, typeof(ImageWatermark));
- resultedImage = ProcessImage(imageFile, this.OutputDir, concreateWatermark);
+ resultedImage = _pipeline.ProcessImage(imageFile, _outputDir, imageWm);
}
- if (typeof(T).IsAssignableTo(typeof(TextWatermark)))
+ else if (watermark is TextWatermark textWm)
{
- var concreateWatermark = (TextWatermark)Convert.ChangeType(watermark, typeof(TextWatermark));
- resultedImage = ProcessImage(imageFile, this.OutputDir, concreateWatermark);
+ resultedImage = _pipeline.ProcessImage(imageFile, _outputDir, textWm);
}
+
if (resultedImage != null)
processedImages.Add(resultedImage);
}
+
return processedImages;
}
///
/// Applies an image watermark to a single image.
///
- /// Path to source image file.
- /// Output directory for processed image.
- /// Image watermark configuration.
- /// Processed image information or null on failure.
- /// Thrown when source image or watermark image is missing.
+ [Obsolete("Use WatermarkPipeline.ProcessImage instead.")]
public ResultImage? ProcessImage(string imagePath, string outputDirectory, ImageWatermark watermark)
{
- if (!File.Exists(imagePath)) { throw new FileNotFoundException("Source file not found!", imagePath); }
- if (!File.Exists(watermark.ImagePath)) { throw new FileNotFoundException("Watermark file not found!", imagePath); }
- if (!Directory.Exists(outputDirectory)) { Directory.CreateDirectory(outputDirectory); }
-
- ResultImage? resultedImage = null;
- using (var targetImage = Image.Load(imagePath))
- using (var watermarkImage = Image.Load(watermark.ImagePath))
+ try
{
- 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)))
- {
- resultedImage = new ResultImage(markedImage, outputDirectory + Path.DirectorySeparatorChar + Path.GetFileName(imagePath));
- markedImage.Save(resultedImage.Path);
- }
+ return _pipeline.ProcessImage(imagePath, outputDirectory, watermark);
+ }
+ catch
+ {
+ return null;
}
- return resultedImage;
}
///
/// Applies a text watermark to a single image.
///
- /// Path to source image file.
- /// Output directory for processed image.
- /// Text watermark configuration.
- /// Processed image information or null on failure.
- /// Thrown when source image is missing.
+ [Obsolete("Use WatermarkPipeline.ProcessImage instead.")]
public ResultImage? ProcessImage(string imagePath, string outputDirectory, TextWatermark watermark)
{
- if(!File.Exists(imagePath)) { throw new FileNotFoundException("Source file not found!", imagePath); }
- if (!Directory.Exists(outputDirectory)) { Directory.CreateDirectory(outputDirectory); }
-
- ResultImage? resultedImage = null;
- using (var targetImage = Image.Load(imagePath))
+ try
{
- using (var markedImage = targetImage.Clone(ctx => this.ApplyScalingWaterMarkText(ctx, watermark)))
- {
- resultedImage = new ResultImage(markedImage, outputDirectory + Path.DirectorySeparatorChar + Path.GetFileName(imagePath));
- markedImage.Save(resultedImage.Path);
- }
+ return _pipeline.ProcessImage(imagePath, outputDirectory, watermark);
}
- return resultedImage;
- }
-
- ///
- /// Calculates origin point for text watermark based on position and size.
- ///
- /// Target image width.
- /// Target image height.
- /// Watermark text size.
- /// Position on target image.
- /// Calculated origin point coordinates.
- private PointF CalcWatermarkOrigin(int width, int height, float watermarkSize, ImagePosition position)
- {
- var origin = new PointF(0, 0);
- //Static value 1 pt is 72 px per inch
- var pixelsPerInch = 72;
- var wmHeight = watermarkSize / pixelsPerInch;
-
- switch (position)
+ catch
{
- case ImagePosition.TopLeft:
- origin = new PointF(watermarkSize / 2, wmHeight / 2);
- break;
- case ImagePosition.TopCenter:
- origin = new PointF(width / 2, wmHeight / 2);
- break;
- case ImagePosition.TopRight:
- origin = new PointF(width - watermarkSize * 2, wmHeight / 2);
- break;
- case ImagePosition.CenterLeft:
- origin = new PointF(watermarkSize / 2, height / 2.5f);
- break;
- case ImagePosition.Center:
- origin = new PointF(width / 2, height / 2.5f);
- break;
- case ImagePosition.CenterRight:
- origin = new PointF(width - watermarkSize * 2, height / 2.5f);
- break;
- case ImagePosition.BottomLeft:
- origin = new PointF(watermarkSize / 2, height - watermarkSize * 2);
- break;
- case ImagePosition.BottomCenter:
- origin = new PointF(width / 2, height - watermarkSize * 2);
- break;
- case ImagePosition.BottomRight:
- origin = new PointF(width - watermarkSize * 2, height - watermarkSize * 2);
- break;
- default:
- break;
+ return null;
}
- return origin;
- }
-
- ///
- /// Calculates origin point for image watermark based on position and dimensions.
- ///
- /// Target image width.
- /// Target image height.
- /// Watermark image width.
- /// Watermark image height.
- /// Position on target image.
- /// Calculated origin point coordinates.
- private Point CalcWatermarkOrigin(int width, int height, int wmWidth,int wmHeight, ImagePosition position)
- {
- var origin = new Point(0, 0);
-
- var wmPaddingX = width - wmWidth;
- var paddingSide = wmPaddingX / 2;
-
- switch (position)
- {
- case ImagePosition.TopLeft:
- origin = new Point(wmWidth / 2, wmHeight / 2);
- break;
- case ImagePosition.TopCenter:
- origin = new Point((int)paddingSide, wmHeight / 2);
- break;
- case ImagePosition.TopRight:
- origin = new Point(width - wmWidth * 2, wmHeight / 2);
- break;
- case ImagePosition.CenterLeft:
- origin = new Point(wmWidth / 2, height / 2);
- break;
- case ImagePosition.Center:
- origin = new Point((int)paddingSide, (int)(height / 2));
- break;
- case ImagePosition.CenterRight:
- origin = new Point(width - wmWidth * 2, height / 2);
- break;
- case ImagePosition.BottomLeft:
- origin = new Point(wmWidth / 2, height - wmHeight);
- break;
- case ImagePosition.BottomCenter:
- origin = new Point((int)paddingSide, height - wmHeight);
- break;
- case ImagePosition.BottomRight:
- origin = new Point(width - wmWidth * 2, height - wmHeight);
- break;
- default:
- break;
- }
- return origin;
- }
-
- ///
- /// Determines horizontal alignment based on watermark position.
- ///
- /// Watermark position on image.
- /// Corresponding horizontal alignment setting.
- private HorizontalAlignment HorizontalAlignmentFromPosition(ImagePosition imagePosition)
- {
- switch (imagePosition)
- {
- case ImagePosition.TopCenter:
- return HorizontalAlignment.Center;
- case ImagePosition.Center:
- return HorizontalAlignment.Center;
- case ImagePosition.BottomCenter:
- return HorizontalAlignment.Center;
- default:
- return HorizontalAlignment.Center;
- }
- return HorizontalAlignment.Left;
- }
-
- ///
- /// Applies text watermark to image with automatic scaling and positioning.
- ///
- /// Image processing context.
- /// Text watermark configuration.
- /// Processing context with applied watermark.
- private IImageProcessingContext ApplyScalingWaterMarkText(IImageProcessingContext processingContext, TextWatermark watermark)
- {
- Size imgSize = processingContext.GetCurrentSize();
- FontRectangle size = TextMeasurer.MeasureSize(watermark.Text, new TextOptions(watermark.Font));
-
- // Find out how much we need to scale the text to fill the space (up or down)
- 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.Layout.Scale));
- //processingContext.SetGraphicsOptions(new GraphicsOptions { AlphaCompositionMode = SixLabors.ImageSharp.PixelFormats.PixelAlphaCompositionMode.Clear});
- //If set, apply backround color
- 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.Layout.Position),
- HorizontalAlignment = HorizontalAlignmentFromPosition(watermark.Layout.Position),
- VerticalAlignment = VerticalAlignment.Top,
- };
-
- 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.Style.Color);
- }
- return processingContext;
- }
- return processingContext
- .DrawText(textOptions, watermark.Text, watermark.Style.Color);
- }
-
- ///
- /// Applies image watermark to target image with scaling and positioning.
- ///
- /// Image processing context.
- /// Image watermark configuration.
- /// Watermark image instance.
- /// Target image being processed.
- /// Processing context with applied watermark.
- private IImageProcessingContext ApplyScalingWaterMarkImage(IImageProcessingContext processingContext, ImageWatermark watermark, Image watermarkImage, Image targetImage)
- {
- var scaleFactor = 1f;
- if (targetImage.Width > targetImage.Height)
- scaleFactor = (float)targetImage.Width / targetImage.Height;
- else
- scaleFactor = (float) targetImage.Height / targetImage.Width;
-
- var wmPaddingX = (targetImage.Width - watermarkImage.Width) / 2;
- var wmPaddingY = (targetImage.Height - watermarkImage.Height) / 2;
- //scaleFactor = 1;
- var minWmPadding = 50;
- var scaledWmWidth = wmPaddingX > minWmPadding && wmPaddingY > minWmPadding
- ? watermarkImage.Width * scaleFactor
- : watermarkImage.Width / scaleFactor;
- var scaledWmHeight = wmPaddingX > minWmPadding && wmPaddingY > minWmPadding
- ? watermarkImage.Height * scaleFactor
- : watermarkImage.Height / scaleFactor;
-
- //If set, apply backround color
- 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.Layout.Position);
-
- 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.Style.Opacity);
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.ToString());
- }
- }
- return processingContext;
- }
-
- return processingContext.DrawImage(watermarkImage, wmPositionOrigin, watermark.Style.Opacity);
}
}
-}
\ No newline at end of file
+}