diff --git a/UnitTest/UnitTest.cs b/UnitTest/UnitTest.cs index be21958..68fac3e 100644 --- a/UnitTest/UnitTest.cs +++ b/UnitTest/UnitTest.cs @@ -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); diff --git a/Watermark.Net/Watermark.Net.sln b/Watermark.Net/Watermark.Net.sln index 7935ff0..e4c5aa0 100644 --- a/Watermark.Net/Watermark.Net.sln +++ b/Watermark.Net/Watermark.Net.sln @@ -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 diff --git a/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs b/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs index a8e6d9d..8b139c2 100644 --- a/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs +++ b/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs @@ -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 /// Watermark configuration. /// List of processed images with watermark information. public List ProcessDirectory(string directory, T watermark) - where T : WatermarkImageBase + where T : IWatermarkDefinition { List processedImages = new List(); 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); } /// @@ -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); } } } \ No newline at end of file diff --git a/Watermark.Net/src/WatermarkNet.Models/Definitions/IWatermarkDefinition.cs b/Watermark.Net/src/WatermarkNet.Models/Definitions/IWatermarkDefinition.cs new file mode 100644 index 0000000..e3eb823 --- /dev/null +++ b/Watermark.Net/src/WatermarkNet.Models/Definitions/IWatermarkDefinition.cs @@ -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; } + } +} diff --git a/Watermark.Net/src/WatermarkNet.Models/Definitions/ImageWatermark.cs b/Watermark.Net/src/WatermarkNet.Models/Definitions/ImageWatermark.cs new file mode 100644 index 0000000..b22b33f --- /dev/null +++ b/Watermark.Net/src/WatermarkNet.Models/Definitions/ImageWatermark.cs @@ -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(); + } +} diff --git a/Watermark.Net/src/WatermarkNet.Models/Definitions/TextWatermark.cs b/Watermark.Net/src/WatermarkNet.Models/Definitions/TextWatermark.cs new file mode 100644 index 0000000..e444183 --- /dev/null +++ b/Watermark.Net/src/WatermarkNet.Models/Definitions/TextWatermark.cs @@ -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(); + } +} diff --git a/Watermark.Net/src/WatermarkNet.Models/Layout/WatermarkLayout.cs b/Watermark.Net/src/WatermarkNet.Models/Layout/WatermarkLayout.cs new file mode 100644 index 0000000..5766c38 --- /dev/null +++ b/Watermark.Net/src/WatermarkNet.Models/Layout/WatermarkLayout.cs @@ -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; } + + /// + /// Gets or sets the padding space around the watermark in pixels. + /// Default: 10px + /// + /// Thrown when value is negative. + public float Padding { get; set; } + } +} diff --git a/Watermark.Net/src/WatermarkNet.Models/Styling/WatermarkStyle.cs b/Watermark.Net/src/WatermarkNet.Models/Styling/WatermarkStyle.cs new file mode 100644 index 0000000..911610c --- /dev/null +++ b/Watermark.Net/src/WatermarkNet.Models/Styling/WatermarkStyle.cs @@ -0,0 +1,21 @@ +using SixLabors.Fonts; +using SixLabors.ImageSharp; + +namespace Watermark.Net.src.WatermarkNet.Models.Styling +{ + public class WatermarkStyle + { + /// + /// Gets or sets the transparency level of the watermark. + /// Range: 0.0 (fully transparent) to 1.0 (completely opaque). + /// Default: 1.0 + /// + /// + /// Thrown when value is outside the 0.0-1.0 range. + /// + public float Opacity { get; set; } + + public bool Pave { get; set; } + public Color Color { get; set; } + } +} diff --git a/Watermark.Net/src/WatermarkNet.Types/ImageWatermark.cs b/Watermark.Net/src/WatermarkNet.Types/ImageWatermark.cs deleted file mode 100644 index 8beb8e0..0000000 --- a/Watermark.Net/src/WatermarkNet.Types/ImageWatermark.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.IO; - -namespace Watermark.Net.src.WatermarkNet.Types -{ - /// - /// Represents a configurable image-based watermark with scaling, positioning, and transparency controls. - /// - public class ImageWatermark : WatermarkImageBase - { - private string _imagePath; - private float? _opacity; - - /// - /// Gets or sets the absolute path to the watermark image file. - /// Supported formats: PNG, JPEG, BMP, GIF. - /// - /// - /// Thrown when setting a value that doesn't point to an existing file. - /// - public string ImagePath - { - get => _imagePath; - set - { - if (!File.Exists(value)) - throw new FileNotFoundException("Watermark image not found", value); - - _imagePath = value; - } - } - - /// - /// Gets or sets the transparency level of the watermark. - /// Range: 0.0 (fully transparent) to 1.0 (completely opaque). - /// Default: 1.0 - /// - /// - /// Thrown when value is outside the 0.0-1.0 range. - /// - 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; - } - } - - /// - /// Gets or sets the scale factor applied to the watermark image relative to the target image. - /// Default: 0.2 (20% of target image width) - /// - public float Scale { get; set; } = 0.2f; - - /// - /// Initializes a new instance of the ImageWatermark class with specified image path. - /// - /// Path to the watermark image file. - public ImageWatermark(string imagePath) : this() - { - ImagePath = imagePath; - } - - /// - /// Initializes a new instance of the ImageWatermark class with default values. - /// - public ImageWatermark() { } - - } -} \ No newline at end of file diff --git a/Watermark.Net/src/WatermarkNet.Types/TextWatermark.cs b/Watermark.Net/src/WatermarkNet.Types/TextWatermark.cs deleted file mode 100644 index 1ca68d3..0000000 --- a/Watermark.Net/src/WatermarkNet.Types/TextWatermark.cs +++ /dev/null @@ -1,88 +0,0 @@ -using SixLabors.Fonts; -using SixLabors.ImageSharp; - -namespace Watermark.Net.src.WatermarkNet.Types -{ - /// - /// Represents a configurable text-based watermark with styling, positioning, and rendering options. - /// - public class TextWatermark : WatermarkImageBase - { - private string _text; - private Font _font; - private float _padding; - private Color _color; - - /// - /// Gets or sets the color of the text watermark. - /// Default: White (#FFFFFF) - /// - public Color Color - { - get => _color; - set => _color = value; - } - - /// - /// Gets or sets the text content for the watermark. - /// - /// Thrown when value is null or whitespace. - public string Text - { - get => _text; - set - { - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentException("Watermark text cannot be empty", nameof(Text)); - _text = value; - } - } - - /// - /// Gets or sets the font used to render the text watermark. - /// Default: Arial 12pt - /// - /// Thrown when value is null. - public Font Font - { - get => _font; - set => _font = value ?? throw new ArgumentNullException(nameof(Font)); - } - - /// - /// Gets or sets the padding space around the text watermark in pixels. - /// Default: 10px - /// - /// Thrown when value is negative. - public float Padding - { - get => _padding; - set - { - if (value < 0) throw new ArgumentOutOfRangeException(nameof(Padding), "Padding cannot be negative"); - _padding = value; - } - } - - /// - /// Gets or sets the rotation angle for the text watermark in degrees. - /// Default: 0 (no rotation) - /// - public float Rotation { get; set; } = 0; - - /// - /// Initializes a new instance of the TextWatermark class with default values. - /// - 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; - } - } -} \ No newline at end of file diff --git a/Watermark.Net/src/WatermarkNet.Types/WatermarkImageBase.cs b/Watermark.Net/src/WatermarkNet.Types/WatermarkImageBase.cs deleted file mode 100644 index a9dfbc0..0000000 --- a/Watermark.Net/src/WatermarkNet.Types/WatermarkImageBase.cs +++ /dev/null @@ -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; - } - } -}