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 +}