diff --git a/UnitTest/TestImages/1.jpg b/UnitTest/TestImages/1.jpg
new file mode 100644
index 0000000..7fb175b
Binary files /dev/null and b/UnitTest/TestImages/1.jpg differ
diff --git a/UnitTest/TestImages/2.png b/UnitTest/TestImages/2.png
new file mode 100644
index 0000000..8a584da
Binary files /dev/null and b/UnitTest/TestImages/2.png differ
diff --git a/UnitTest/TestImages/3.png b/UnitTest/TestImages/3.png
new file mode 100644
index 0000000..a4b904d
Binary files /dev/null and b/UnitTest/TestImages/3.png differ
diff --git a/UnitTest/TestImages/4.jpg b/UnitTest/TestImages/4.jpg
new file mode 100644
index 0000000..40e3dce
Binary files /dev/null and b/UnitTest/TestImages/4.jpg differ
diff --git a/UnitTest/TestImages/5.jpg b/UnitTest/TestImages/5.jpg
new file mode 100644
index 0000000..4a11b5f
Binary files /dev/null and b/UnitTest/TestImages/5.jpg differ
diff --git a/UnitTest/TestImages/6.jpg b/UnitTest/TestImages/6.jpg
new file mode 100644
index 0000000..6088e28
Binary files /dev/null and b/UnitTest/TestImages/6.jpg differ
diff --git a/UnitTest/TestImages/7.jpg b/UnitTest/TestImages/7.jpg
new file mode 100644
index 0000000..20ab5a1
Binary files /dev/null and b/UnitTest/TestImages/7.jpg differ
diff --git a/UnitTest/TestImages/sample_wm.png b/UnitTest/TestImages/sample_wm.png
new file mode 100644
index 0000000..f947f20
Binary files /dev/null and b/UnitTest/TestImages/sample_wm.png differ
diff --git a/UnitTest/UnitTest.cs b/UnitTest/UnitTest.cs
new file mode 100644
index 0000000..9dc8fb5
--- /dev/null
+++ b/UnitTest/UnitTest.cs
@@ -0,0 +1,76 @@
+using SixLabors.Fonts;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using Watermark.Net.src.WatermarkNet.Core;
+using Watermark.Net.src.WatermarkNet.Types;
+
+namespace UnitTest
+{
+ [TestClass]
+ public class UnitTest
+ {
+ [TestMethod]
+ public void TextWatermarkTest()
+ {
+ var watermarker = new Watermarker();
+ var watermark = new TextWatermark();
+
+ watermark.Text = "Test";
+ watermark.Color = Color.White;
+ watermark.Font = SystemFonts.CreateFont("Arial", 1);
+ watermark.Position = Watermark.Net.src.WatermarkNet.Enums.ImagePosition.BottomCenter;
+ watermark.RotateAngle = 90;
+ var resultedImage = watermarker.ProcessImage("TestImages/2.png", "test/text", watermark);
+
+ Assert.IsTrue(File.Exists(resultedImage.Path));
+ Assert.IsNotNull(resultedImage);
+ }
+
+ [TestMethod]
+ public void ImageWatermarkTest()
+ {
+ var watermarker = new Watermarker();
+ var watermark = new ImageWatermark();
+
+ 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));
+ Assert.IsNotNull(resultedImage);
+ }
+
+ [TestMethod]
+ public void TextWatermarkDirectoryProccessTest()
+ {
+ var watermarker = new Watermarker("test/text/pave");
+ var watermark = new TextWatermark();
+
+ watermark.Text = "Test";
+ watermark.Color = Rgba32.ParseHex("FFFFFF50");
+ watermark.Font = SystemFonts.CreateFont("Arial", 14);
+ watermark.Scale = 1f;
+ watermark.Position = Watermark.Net.src.WatermarkNet.Enums.ImagePosition.TopLeft;
+ watermark.Pave = true;
+ watermarker.ProcessDirectory("TestImages", watermark);
+
+ Assert.IsTrue(Directory.GetFiles(watermarker.OutputDir)?.Length > 0);
+ }
+
+ [TestMethod]
+ public void ImageWatermarkDirectoryProccessTest()
+ {
+ var watermarker = new Watermarker("test/image/pave");
+ var watermark = new ImageWatermark();
+
+ watermark.ImagePath = "TestImages/sample_wm.png";
+ watermark.Position = Watermark.Net.src.WatermarkNet.Enums.ImagePosition.Center;
+ watermark.Scale = 1;
+ watermark.Pave = true;
+ var resultedImage = watermarker.ProcessDirectory("TestImages", watermark);
+
+ Assert.IsTrue(Directory.GetFiles(watermarker.OutputDir)?.Length > 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTest/UnitTest.csproj b/UnitTest/UnitTest.csproj
new file mode 100644
index 0000000..a14f794
--- /dev/null
+++ b/UnitTest/UnitTest.csproj
@@ -0,0 +1,51 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+all
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/UnitTest/Usings.cs b/UnitTest/Usings.cs
new file mode 100644
index 0000000..0a0df86
--- /dev/null
+++ b/UnitTest/Usings.cs
@@ -0,0 +1,2 @@
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
+global using Watermark;
\ No newline at end of file
diff --git a/Watermark.Net/.vs/ProjectEvaluation/watermark.net.metadata.v9.bin b/Watermark.Net/.vs/ProjectEvaluation/watermark.net.metadata.v9.bin
new file mode 100644
index 0000000..ece6ad6
Binary files /dev/null and b/Watermark.Net/.vs/ProjectEvaluation/watermark.net.metadata.v9.bin differ
diff --git a/Watermark.Net/.vs/ProjectEvaluation/watermark.net.projects.v9.bin b/Watermark.Net/.vs/ProjectEvaluation/watermark.net.projects.v9.bin
new file mode 100644
index 0000000..dd58c52
Binary files /dev/null and b/Watermark.Net/.vs/ProjectEvaluation/watermark.net.projects.v9.bin differ
diff --git a/Watermark.Net/.vs/ProjectEvaluation/watermark.net.strings.v9.bin b/Watermark.Net/.vs/ProjectEvaluation/watermark.net.strings.v9.bin
new file mode 100644
index 0000000..99160ee
Binary files /dev/null and b/Watermark.Net/.vs/ProjectEvaluation/watermark.net.strings.v9.bin differ
diff --git a/Watermark.Net/.vs/Watermark.Net/CopilotIndices/17.14.734.62261/CodeChunks.db b/Watermark.Net/.vs/Watermark.Net/CopilotIndices/17.14.734.62261/CodeChunks.db
new file mode 100644
index 0000000..156ed92
Binary files /dev/null and b/Watermark.Net/.vs/Watermark.Net/CopilotIndices/17.14.734.62261/CodeChunks.db differ
diff --git a/Watermark.Net/.vs/Watermark.Net/CopilotIndices/17.14.734.62261/SemanticSymbols.db b/Watermark.Net/.vs/Watermark.Net/CopilotIndices/17.14.734.62261/SemanticSymbols.db
new file mode 100644
index 0000000..9660480
Binary files /dev/null and b/Watermark.Net/.vs/Watermark.Net/CopilotIndices/17.14.734.62261/SemanticSymbols.db differ
diff --git a/Watermark.Net/.vs/Watermark.Net/DesignTimeBuild/.dtbcache.v2 b/Watermark.Net/.vs/Watermark.Net/DesignTimeBuild/.dtbcache.v2
new file mode 100644
index 0000000..0e09f6e
Binary files /dev/null and b/Watermark.Net/.vs/Watermark.Net/DesignTimeBuild/.dtbcache.v2 differ
diff --git a/Watermark.Net/.vs/Watermark.Net/FileContentIndex/0c5a11ba-d38c-4c39-88f2-28df019c6099.vsidx b/Watermark.Net/.vs/Watermark.Net/FileContentIndex/0c5a11ba-d38c-4c39-88f2-28df019c6099.vsidx
new file mode 100644
index 0000000..12aa741
Binary files /dev/null and b/Watermark.Net/.vs/Watermark.Net/FileContentIndex/0c5a11ba-d38c-4c39-88f2-28df019c6099.vsidx differ
diff --git a/Watermark.Net/.vs/Watermark.Net/v17/.futdcache.v2 b/Watermark.Net/.vs/Watermark.Net/v17/.futdcache.v2
new file mode 100644
index 0000000..f811bfc
Binary files /dev/null and b/Watermark.Net/.vs/Watermark.Net/v17/.futdcache.v2 differ
diff --git a/Watermark.Net/.vs/Watermark.Net/v17/.suo b/Watermark.Net/.vs/Watermark.Net/v17/.suo
new file mode 100644
index 0000000..04e14dc
Binary files /dev/null and b/Watermark.Net/.vs/Watermark.Net/v17/.suo differ
diff --git a/Watermark.Net/.vs/Watermark.Net/v17/DocumentLayout.backup.json b/Watermark.Net/.vs/Watermark.Net/v17/DocumentLayout.backup.json
new file mode 100644
index 0000000..c3175b5
--- /dev/null
+++ b/Watermark.Net/.vs/Watermark.Net/v17/DocumentLayout.backup.json
@@ -0,0 +1,132 @@
+{
+ "Version": 1,
+ "WorkspaceRootPath": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\",
+ "Documents": [
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\watermark.net.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:watermark.net.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\watermark.net.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:watermark.net.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\src\\watermarknet.common\\watermarker.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:src\\watermarknet.common\\watermarker.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\src\\watermarknet.types\\sourceimage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:src\\watermarknet.types\\sourceimage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\src\\watermarknet.types\\imagewatermark.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:src\\watermarknet.types\\imagewatermark.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\src\\watermarknet.types\\textwatermark.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:src\\watermarknet.types\\textwatermark.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|D:\\dev\\Watermark.NET\\UnitTest\\UnitTest.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ }
+ ],
+ "DocumentGroupContainers": [
+ {
+ "Orientation": 0,
+ "VerticalTabListWidth": 256,
+ "DocumentGroups": [
+ {
+ "DockedWidth": 200,
+ "SelectedChildIndex": 0,
+ "Children": [
+ {
+ "$type": "Document",
+ "DocumentIndex": 0,
+ "Title": "Watermark.Net.csproj",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\Watermark.Net.csproj",
+ "RelativeDocumentMoniker": "Watermark.Net.csproj",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\Watermark.Net.csproj",
+ "RelativeToolTip": "Watermark.Net.csproj",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
+ "WhenOpened": "2025-07-23T00:08:42.641Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 4,
+ "Title": "ImageWatermark.cs",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\ImageWatermark.cs",
+ "RelativeDocumentMoniker": "src\\WatermarkNet.Types\\ImageWatermark.cs",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\ImageWatermark.cs",
+ "RelativeToolTip": "src\\WatermarkNet.Types\\ImageWatermark.cs",
+ "ViewState": "AgIAAA4AAAAAAAAAAAAQwEMAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-22T00:18:31.318Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 3,
+ "Title": "SourceImage.cs",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\SourceImage.cs",
+ "RelativeDocumentMoniker": "src\\WatermarkNet.Types\\SourceImage.cs",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\SourceImage.cs",
+ "RelativeToolTip": "src\\WatermarkNet.Types\\SourceImage.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-22T00:18:29.952Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 1,
+ "Title": "Watermark.Net",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\Watermark.Net.csproj",
+ "RelativeDocumentMoniker": "Watermark.Net.csproj",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\Watermark.Net.csproj",
+ "RelativeToolTip": "Watermark.Net.csproj",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
+ "WhenOpened": "2025-07-22T00:16:02.548Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 6,
+ "Title": "UnitTest.cs",
+ "DocumentMoniker": "D:\\dev\\Watermark.NET\\UnitTest\\UnitTest.cs",
+ "RelativeDocumentMoniker": "..\\..\\..\\Watermark.NET\\UnitTest\\UnitTest.cs",
+ "ToolTip": "D:\\dev\\Watermark.NET\\UnitTest\\UnitTest.cs",
+ "RelativeToolTip": "..\\..\\..\\Watermark.NET\\UnitTest\\UnitTest.cs",
+ "ViewState": "AgIAABgAAAAAAAAAAADgvyYAAAABAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-22T00:10:06.88Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 5,
+ "Title": "TextWatermark.cs",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\TextWatermark.cs",
+ "RelativeDocumentMoniker": "src\\WatermarkNet.Types\\TextWatermark.cs",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\TextWatermark.cs",
+ "RelativeToolTip": "src\\WatermarkNet.Types\\TextWatermark.cs",
+ "ViewState": "AgIAABoAAAAAAAAAAAAUwFIAAAABAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-21T23:58:35.959Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 2,
+ "Title": "Watermarker.cs",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Common\\Watermarker.cs",
+ "RelativeDocumentMoniker": "src\\WatermarkNet.Common\\Watermarker.cs",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Common\\Watermarker.cs",
+ "RelativeToolTip": "src\\WatermarkNet.Common\\Watermarker.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAABUAAAAcAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-15T22:39:18.693Z"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Watermark.Net/.vs/Watermark.Net/v17/DocumentLayout.json b/Watermark.Net/.vs/Watermark.Net/v17/DocumentLayout.json
new file mode 100644
index 0000000..c3175b5
--- /dev/null
+++ b/Watermark.Net/.vs/Watermark.Net/v17/DocumentLayout.json
@@ -0,0 +1,132 @@
+{
+ "Version": 1,
+ "WorkspaceRootPath": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\",
+ "Documents": [
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\watermark.net.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:watermark.net.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\watermark.net.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:watermark.net.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\src\\watermarknet.common\\watermarker.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:src\\watermarknet.common\\watermarker.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\src\\watermarknet.types\\sourceimage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:src\\watermarknet.types\\sourceimage.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\src\\watermarknet.types\\imagewatermark.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:src\\watermarknet.types\\imagewatermark.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|d:\\dev\\getready\\watermark.net\\watermark.net\\src\\watermarknet.types\\textwatermark.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}|Watermark.Net.csproj|solutionrelative:src\\watermarknet.types\\textwatermark.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|D:\\dev\\Watermark.NET\\UnitTest\\UnitTest.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ }
+ ],
+ "DocumentGroupContainers": [
+ {
+ "Orientation": 0,
+ "VerticalTabListWidth": 256,
+ "DocumentGroups": [
+ {
+ "DockedWidth": 200,
+ "SelectedChildIndex": 0,
+ "Children": [
+ {
+ "$type": "Document",
+ "DocumentIndex": 0,
+ "Title": "Watermark.Net.csproj",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\Watermark.Net.csproj",
+ "RelativeDocumentMoniker": "Watermark.Net.csproj",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\Watermark.Net.csproj",
+ "RelativeToolTip": "Watermark.Net.csproj",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
+ "WhenOpened": "2025-07-23T00:08:42.641Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 4,
+ "Title": "ImageWatermark.cs",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\ImageWatermark.cs",
+ "RelativeDocumentMoniker": "src\\WatermarkNet.Types\\ImageWatermark.cs",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\ImageWatermark.cs",
+ "RelativeToolTip": "src\\WatermarkNet.Types\\ImageWatermark.cs",
+ "ViewState": "AgIAAA4AAAAAAAAAAAAQwEMAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-22T00:18:31.318Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 3,
+ "Title": "SourceImage.cs",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\SourceImage.cs",
+ "RelativeDocumentMoniker": "src\\WatermarkNet.Types\\SourceImage.cs",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\SourceImage.cs",
+ "RelativeToolTip": "src\\WatermarkNet.Types\\SourceImage.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-22T00:18:29.952Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 1,
+ "Title": "Watermark.Net",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\Watermark.Net.csproj",
+ "RelativeDocumentMoniker": "Watermark.Net.csproj",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\Watermark.Net.csproj",
+ "RelativeToolTip": "Watermark.Net.csproj",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
+ "WhenOpened": "2025-07-22T00:16:02.548Z",
+ "EditorCaption": ""
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 6,
+ "Title": "UnitTest.cs",
+ "DocumentMoniker": "D:\\dev\\Watermark.NET\\UnitTest\\UnitTest.cs",
+ "RelativeDocumentMoniker": "..\\..\\..\\Watermark.NET\\UnitTest\\UnitTest.cs",
+ "ToolTip": "D:\\dev\\Watermark.NET\\UnitTest\\UnitTest.cs",
+ "RelativeToolTip": "..\\..\\..\\Watermark.NET\\UnitTest\\UnitTest.cs",
+ "ViewState": "AgIAABgAAAAAAAAAAADgvyYAAAABAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-22T00:10:06.88Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 5,
+ "Title": "TextWatermark.cs",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\TextWatermark.cs",
+ "RelativeDocumentMoniker": "src\\WatermarkNet.Types\\TextWatermark.cs",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Types\\TextWatermark.cs",
+ "RelativeToolTip": "src\\WatermarkNet.Types\\TextWatermark.cs",
+ "ViewState": "AgIAABoAAAAAAAAAAAAUwFIAAAABAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-21T23:58:35.959Z"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 2,
+ "Title": "Watermarker.cs",
+ "DocumentMoniker": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Common\\Watermarker.cs",
+ "RelativeDocumentMoniker": "src\\WatermarkNet.Common\\Watermarker.cs",
+ "ToolTip": "D:\\dev\\GetReady\\Watermark.Net\\Watermark.Net\\src\\WatermarkNet.Common\\Watermarker.cs",
+ "RelativeToolTip": "src\\WatermarkNet.Common\\Watermarker.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAABUAAAAcAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-07-15T22:39:18.693Z"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Watermark.Net/.vs/Watermark.Net/v17/TestStore/0/000.testlog b/Watermark.Net/.vs/Watermark.Net/v17/TestStore/0/000.testlog
new file mode 100644
index 0000000..772d753
Binary files /dev/null and b/Watermark.Net/.vs/Watermark.Net/v17/TestStore/0/000.testlog differ
diff --git a/Watermark.Net/.vs/Watermark.Net/v17/TestStore/0/testlog.manifest b/Watermark.Net/.vs/Watermark.Net/v17/TestStore/0/testlog.manifest
new file mode 100644
index 0000000..e92ede2
Binary files /dev/null and b/Watermark.Net/.vs/Watermark.Net/v17/TestStore/0/testlog.manifest differ
diff --git a/Watermark.Net/Watermark.Net.csproj b/Watermark.Net/Watermark.Net.csproj
new file mode 100644
index 0000000..5cd7032
--- /dev/null
+++ b/Watermark.Net/Watermark.Net.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net7.0
+ enable
+ enable
+ True
+ 0.25.7.23
+ Waternark.NET
+ Geckon01
+ Watermark.Net is .NET library for adding text and image watermarks to images. Built on SixLabors.ImageSharp, it provides a simple yet comprehensive API for all your watermarking needs.
+ 0.25.7.23
+ 0.25.7.23
+
+
+
+
+
+
+
+
diff --git a/Watermark.Net/Watermark.Net.sln b/Watermark.Net/Watermark.Net.sln
new file mode 100644
index 0000000..743f2ca
--- /dev/null
+++ b/Watermark.Net/Watermark.Net.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33213.308
+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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "..\Sample\Sample.csproj", "{E28B3D42-E65D-4178-A25B-8B76FC6F9673}"
+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
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5000C6D8-A7CD-4815-A724-EEE51AD8AFDA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {26E6D8A0-CFB1-4A30-A4DD-D6DBBDADB388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {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
+ {E28B3D42-E65D-4178-A25B-8B76FC6F9673}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E28B3D42-E65D-4178-A25B-8B76FC6F9673}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E28B3D42-E65D-4178-A25B-8B76FC6F9673}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E28B3D42-E65D-4178-A25B-8B76FC6F9673}.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
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {BDD35E29-9EA2-4833-B2F4-26E051B7AF10}
+ EndGlobalSection
+EndGlobal
diff --git a/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs b/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs
new file mode 100644
index 0000000..c7e36c0
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Common/Watermarker.cs
@@ -0,0 +1,342 @@
+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.Types;
+using static System.Net.Mime.MediaTypeNames;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+using Image = SixLabors.ImageSharp.Image;
+
+namespace Watermark.Net.src.WatermarkNet.Core
+{
+ public class Watermarker
+ {
+ private string _outputDir;
+
+ ///
+ /// Gets or sets the output directory for processed images.
+ ///
+ public string OutputDir { get { return _outputDir; } set { _outputDir = value; } }
+ public Watermarker()
+ {
+
+ }
+
+ public Watermarker(string outputDir)
+ {
+ _outputDir = outputDir;
+ }
+
+ ///
+ /// Processes all images in a directory applying watermark.
+ ///
+ /// Source directory containing images to process.
+ /// Watermark configuration.
+ /// List of processed images with watermark information.
+ public List ProcessDirectory(string directory, T watermark)
+ where T : WatermarkImageBase
+ {
+ 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;
+
+ WmarkedImage? resultedImage = null;
+ if (typeof(T).IsAssignableTo(typeof(ImageWatermark)))
+ {
+ var concreateWatermark = (ImageWatermark)Convert.ChangeType(watermark, typeof(ImageWatermark));
+ resultedImage = ProcessImage(imageFile, this.OutputDir, concreateWatermark);
+ }
+ if (typeof(T).IsAssignableTo(typeof(TextWatermark)))
+ {
+ var concreateWatermark = (TextWatermark)Convert.ChangeType(watermark, typeof(TextWatermark));
+ resultedImage = ProcessImage(imageFile, this.OutputDir, concreateWatermark);
+ }
+ 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.
+ public WmarkedImage? 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); }
+
+ WmarkedImage? resultedImage = null;
+ 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);
+ watermarkImage.Mutate(x => x.Resize(new Size(scaledWmWidth, scaledWmHeight)));
+
+ using (var markedImage = targetImage.Clone(ctx => this.ApplyScalingWaterMarkImage(ctx, watermark, watermarkImage, targetImage)))
+ {
+ resultedImage = new WmarkedImage(markedImage, outputDirectory + Path.DirectorySeparatorChar + Path.GetFileName(imagePath));
+ markedImage.Save(resultedImage.Path);
+ }
+ }
+ 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.
+ public WmarkedImage? 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); }
+
+ WmarkedImage? resultedImage = null;
+ using (var targetImage = Image.Load(imagePath))
+ {
+ using (var markedImage = targetImage.Clone(ctx => this.ApplyScalingWaterMarkText(ctx, watermark)))
+ {
+ resultedImage = new WmarkedImage(markedImage, outputDirectory + Path.DirectorySeparatorChar + Path.GetFileName(imagePath));
+ markedImage.Save(resultedImage.Path);
+ }
+ }
+ 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)
+ {
+ 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.
+ ///
+ /// 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;
+ break;
+ case ImagePosition.Center:
+ return HorizontalAlignment.Center;
+ break;
+ case ImagePosition.BottomCenter:
+ return HorizontalAlignment.Center;
+ break;
+ }
+ 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
+ Font scaledFont = new Font(watermark.Font, scalingFactor / 16 * (watermark.Font.Size * watermark.Scale));
+ ImagePosition[] centerImagePositions = { ImagePosition.CenterLeft, ImagePosition.CenterRight, ImagePosition.Center };
+ //processingContext.SetGraphicsOptions(new GraphicsOptions { AlphaCompositionMode = SixLabors.ImageSharp.PixelFormats.PixelAlphaCompositionMode.Clear});
+ //If set, apply backround color
+ if (watermark.BackroundColor != null)
+ processingContext.BackgroundColor((Color)watermark.BackroundColor);
+
+ var textOptions = new RichTextOptions(scaledFont)
+ {
+ ColorFontSupport = ColorFontSupport.MicrosoftColrFormat,
+ Origin = CalcWatermarkOrigin(imgSize.Width, imgSize.Height, scaledFont.Size, watermark.Position),
+ HorizontalAlignment = HorizontalAlignmentFromPosition(watermark.Position),
+ VerticalAlignment = VerticalAlignment.Top,
+ };
+
+ if (watermark.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);
+ }
+ return processingContext;
+ }
+ return processingContext
+ .DrawText(textOptions, watermark.Text, watermark.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.BackroundColor != null)
+ processingContext.BackgroundColor((Color)watermark.BackroundColor);
+
+ watermarkImage.Mutate(x => x.Resize(new Size((int)scaledWmWidth, (int)scaledWmHeight)));
+ var wmPositionOrigin = CalcWatermarkOrigin(targetImage.Width, targetImage.Height, watermarkImage.Width, watermarkImage.Height, watermark.Position);
+
+ if (watermark.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);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.ToString());
+ }
+ }
+ return processingContext;
+ }
+
+ return processingContext.DrawImage(watermarkImage, wmPositionOrigin, watermark.Opacity);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Watermark.Net/src/WatermarkNet.Enums/ImagePosition.cs b/Watermark.Net/src/WatermarkNet.Enums/ImagePosition.cs
new file mode 100644
index 0000000..e752a38
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Enums/ImagePosition.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Watermark.Net.src.WatermarkNet.Enums
+{
+ public enum ImagePosition
+ {
+ TopLeft = 0,
+ TopCenter = 1,
+ TopRight = 2,
+ CenterLeft = 3,
+ Center = 4,
+ CenterRight = 5,
+ BottomLeft = 6,
+ BottomCenter = 7,
+ BottomRight = 8
+ }
+}
diff --git a/Watermark.Net/src/WatermarkNet.Types/ImageWatermark.cs b/Watermark.Net/src/WatermarkNet.Types/ImageWatermark.cs
new file mode 100644
index 0000000..8beb8e0
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Types/ImageWatermark.cs
@@ -0,0 +1,75 @@
+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/SourceImage.cs b/Watermark.Net/src/WatermarkNet.Types/SourceImage.cs
new file mode 100644
index 0000000..cb06844
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Types/SourceImage.cs
@@ -0,0 +1,13 @@
+using SixLabors.ImageSharp;
+
+namespace Watermark.Net.src.WatermarkNet.Types
+{
+ internal class SourceImage
+ {
+ private string _path;
+ private Image _image;
+
+ public Image Image => _image;
+ public String Path => _path;
+ }
+}
diff --git a/Watermark.Net/src/WatermarkNet.Types/TextWatermark.cs b/Watermark.Net/src/WatermarkNet.Types/TextWatermark.cs
new file mode 100644
index 0000000..c334a71
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Types/TextWatermark.cs
@@ -0,0 +1,83 @@
+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()
+ {
+ _color = Color.White;
+ _font = SystemFonts.CreateFont("Arial", 12);
+ _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
new file mode 100644
index 0000000..a9dfbc0
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Types/WatermarkImageBase.cs
@@ -0,0 +1,60 @@
+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;
+ }
+ }
+}
diff --git a/Watermark.Net/src/WatermarkNet.Types/WmarkedImage.cs b/Watermark.Net/src/WatermarkNet.Types/WmarkedImage.cs
new file mode 100644
index 0000000..cfc14c6
--- /dev/null
+++ b/Watermark.Net/src/WatermarkNet.Types/WmarkedImage.cs
@@ -0,0 +1,15 @@
+using SixLabors.ImageSharp;
+
+namespace Watermark.Net.src.WatermarkNet.Types
+{
+ public class WmarkedImage
+ {
+ public string Path { get; }
+ public Image Image { get; }
+ public WmarkedImage(Image image, string path)
+ {
+ this.Image = image;
+ this.Path= path;
+ }
+ }
+}
diff --git a/Watrmark.Net CLI/Extensions.cs b/Watrmark.Net CLI/Extensions.cs
new file mode 100644
index 0000000..422ff1b
--- /dev/null
+++ b/Watrmark.Net CLI/Extensions.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Watrmark.Net_CLI
+{
+ internal class Extensions
+ {
+ private static void ClearRow(int row)
+ {
+ Console.SetCursorPosition(0, row);
+ Console.Write(new String(' ', Console.WindowWidth));
+ Console.SetCursorPosition(0, row);
+ }
+
+ private static void DrawTextProgressBar(double percentComplite)
+ {
+ int totalChunks = Console.WindowWidth / 2;
+
+ //draw empty progress bar
+ Console.CursorLeft = 0;
+ Console.Write("["); //start
+ Console.CursorLeft = totalChunks + 1;
+ Console.Write("]"); //end
+ Console.CursorLeft = 1;
+
+ int numChunksComplete = Convert.ToInt16(totalChunks * percentComplite);
+
+ //draw completed chunks
+ Console.BackgroundColor = ConsoleColor.Green;
+ Console.Write("".PadRight(numChunksComplete));
+
+ //draw incomplete chunks
+ Console.BackgroundColor = ConsoleColor.Gray;
+ Console.Write("".PadRight(totalChunks - numChunksComplete));
+
+ //draw totals
+ Console.CursorLeft = totalChunks + 5;
+ Console.BackgroundColor = ConsoleColor.Black;
+ }
+
+ public static void DrawStats(string imagePath, int filesComplite, int filesTotal, Stopwatch stopwatch)
+ {
+ var complitePercent = Convert.ToDouble(filesComplite) / filesTotal;
+ var operationsPerSecond = Convert.ToDouble(filesComplite) / stopwatch.Elapsed.TotalSeconds;
+
+ Console.CursorVisible = false;
+ ClearRow(0);
+ Console.WriteLine($"Processed file: {imagePath}");
+ DrawTextProgressBar(complitePercent);
+ Console.Write($"{Math.Round(complitePercent * 100, 0)}% \t");
+ Console.WriteLine($"{filesComplite} of {filesTotal}");
+ ClearRow(2);
+ Console.WriteLine($"{Math.Round(operationsPerSecond, 0)} per second");
+ }
+
+ public static void DrawCompliteStats(TimeSpan elapsedTime)
+ {
+ Console.Clear();
+ Console.SetCursorPosition(0, 0);
+ Console.ForegroundColor = ConsoleColor.Green;
+ Console.WriteLine($"Work complite in {elapsedTime.Minutes} min. {elapsedTime.TotalSeconds} sec.");
+ Console.ResetColor();
+ }
+ }
+}
diff --git a/Watrmark.Net CLI/Program.cs b/Watrmark.Net CLI/Program.cs
new file mode 100644
index 0000000..62c790b
--- /dev/null
+++ b/Watrmark.Net CLI/Program.cs
@@ -0,0 +1,91 @@
+using CommandLine;
+using Watermark.Net.src.WatermarkNet.Types;
+using System.Diagnostics;
+using Watermark.Net.src.WatermarkNet.Core;
+using Watrmark.Net_CLI.Watermakr.Net.CLI.Enums;
+using Watrmark.Net_CLI.Watermark.Net.CLI.Models;
+using Watrmark.Net_CLI.Watermak.Net.CLI.Constants;
+using Watrmark.Net_CLI;
+using System.Runtime.InteropServices;
+
+Parser.Default.ParseArguments(args)
+ .WithParsed(option => {
+ switch (option.WatermarkType)
+ {
+ case WatermarkType.Image:
+ if (option.FilePath != null)
+ ProccessSingleFile(option);
+ if (option.DirectoryPath != null)
+ ProccessDirectory(option);
+ break;
+ case WatermarkType.Text:
+ if (option.FilePath != null)
+ ProccessSingleText(option);
+ else
+ ProccessDirectoryText(option);
+ break;
+ }
+
+ })
+ .WithNotParsed(error => { });
+
+static void ProccessSingleFile(ConsoleOptions options)
+{
+
+}
+static void ProccessDirectory(ConsoleOptions options)
+{
+
+}
+
+static void ProccessSingleText(ConsoleOptions options)
+{
+
+}
+static void ProccessDirectoryText(ConsoleOptions options)
+{
+ if (options.WatermarkText == null || options.WatermarkText == string.Empty)
+ throw new ArgumentNullException("Watermark text can not be null");
+ if(options.DirectoryPath == null || !Directory.Exists(options.DirectoryPath))
+ throw new ArgumentNullException("Specified files directory not found");
+ if (options.OutputPath == null || !Directory.Exists(options.OutputPath))
+ throw new ArgumentNullException("Specified output directory not found");
+
+ var directoryFiles = Directory.GetFiles(options.DirectoryPath);
+
+ var filesTotal = directoryFiles.Length;
+ var chunkSize = filesTotal / (options.ThreadsNumber ?? Environment.ProcessorCount);
+ var filesChunks = directoryFiles.ToList().Chunk(chunkSize < 1 ? 1: chunkSize);
+ var filesComplite = 0;
+
+ var watermark = new TextWatermark{
+ Text = options.WatermarkText,
+ Color = options.WatermarkColor ?? Constans.DefaultTextColor,
+ Position = options.WatermarkPositon ?? Constans.DefaultWatermarkPosition,
+ BackroundColor = options.WatermarkBackround ?? Constans.DefaultBackroundColor,
+ Font = Constans.DefaultWatermarkFont,
+ Scale = options.WatermarkScale ?? Constans.DefaultWatermarkScale
+ };
+ var watermarker = new Watermarker();
+
+ Stopwatch stopwatch = new Stopwatch();
+ stopwatch.Start();
+
+ Console.Clear();
+
+ Parallel.ForEach(filesChunks, new ParallelOptions { MaxDegreeOfParallelism = options.ThreadsNumber ?? Environment.ProcessorCount }, chunk =>
+ {
+ foreach (var imagePath in chunk)
+ {
+ var resultedImage = watermarker.ProcessImage(imagePath, options.OutputPath, watermark);
+
+ lock (stopwatch)
+ {
+ Extensions.DrawStats(resultedImage.Path, ++filesComplite, filesTotal, stopwatch);
+ }
+ }
+ });
+
+ stopwatch.Stop();
+ Extensions.DrawCompliteStats(stopwatch.Elapsed);
+}
\ No newline at end of file
diff --git a/Watrmark.Net CLI/Properties/launchSettings.json b/Watrmark.Net CLI/Properties/launchSettings.json
new file mode 100644
index 0000000..d47f0e3
--- /dev/null
+++ b/Watrmark.Net CLI/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Watrmark.Net CLI": {
+ "commandName": "Project",
+ "commandLineArgs": "--type Text --text Lorem -p Center -o \"Z:\\Загрузки\\testout\" -d \"Z:\\Загрузки\\test\\1\" --scale 2 --threads 8"
+ }
+ }
+}
\ No newline at end of file
diff --git a/Watrmark.Net CLI/Watermak.Net.CLI.Constants/Constans.cs b/Watrmark.Net CLI/Watermak.Net.CLI.Constants/Constans.cs
new file mode 100644
index 0000000..03e81da
--- /dev/null
+++ b/Watrmark.Net CLI/Watermak.Net.CLI.Constants/Constans.cs
@@ -0,0 +1,14 @@
+using SixLabors.Fonts;
+using Watermark.Net.src.WatermarkNet.Enums;
+
+namespace Watrmark.Net_CLI.Watermak.Net.CLI.Constants
+{
+ internal class Constans
+ {
+ public static readonly SixLabors.ImageSharp.Color DefaultTextColor = SixLabors.ImageSharp.Color.LightGray;
+ public static readonly SixLabors.ImageSharp.Color DefaultBackroundColor = SixLabors.ImageSharp.Color.White;
+ public static readonly ImagePosition DefaultWatermarkPosition = ImagePosition.Center;
+ public static readonly Font DefaultWatermarkFont = SystemFonts.CreateFont("Tahoma", 14);
+ public static readonly float DefaultWatermarkScale = 1;
+ }
+}
diff --git a/Watrmark.Net CLI/Watermakr.Net.CLI.Enums/ConsoleOptions.cs b/Watrmark.Net CLI/Watermakr.Net.CLI.Enums/ConsoleOptions.cs
new file mode 100644
index 0000000..9a06ab2
--- /dev/null
+++ b/Watrmark.Net CLI/Watermakr.Net.CLI.Enums/ConsoleOptions.cs
@@ -0,0 +1,42 @@
+using Watrmark.Net_CLI.Watermark.Net.CLI.Models;
+using Watermark.Net.src.WatermarkNet.Enums;
+using CommandLine;
+
+namespace Watrmark.Net_CLI.Watermakr.Net.CLI.Enums
+{
+ internal class ConsoleOptions
+ {
+ [Option("type", Required = true, HelpText = "")]
+ public WatermarkType WatermarkType { get; set; }
+
+ [Option('f', "file", Required = false, HelpText = "")]
+ public string? FilePath { get; set; }
+
+ [Option('d', "directory", Group = "wmoptions", HelpText = "")]
+ public string? DirectoryPath { get; set; }
+
+ [Option('w', "watermark", Group = "wmoptions", HelpText = "")]
+ public string? WatermarkPath { get; set; }
+
+ [Option('o', "output", Required = true, HelpText = "")]
+ public string? OutputPath { get; set; }
+
+ [Option("text", Group = "wmoptions", HelpText = "")]
+ public string? WatermarkText { get; set; }
+
+ [Option('c', "color", Required = false, HelpText = "")]
+ public SixLabors.ImageSharp.Color? WatermarkColor { get; set; }
+
+ [Option('b', "wmbackroud", Required = false, HelpText = "")]
+ public SixLabors.ImageSharp.Color? WatermarkBackround { get; set; }
+
+ [Option('s', "scale", Required = false, HelpText = "")]
+ public float? WatermarkScale { get; set; }
+
+ [Option('p', "position", Required = false, HelpText = "")]
+ public ImagePosition? WatermarkPositon { get; set; }
+
+ [Option("threads", Required = false, HelpText = "")]
+ public int? ThreadsNumber { get; set; }
+ }
+}
diff --git a/Watrmark.Net CLI/Watermark.Net.CLI.Models/WatermarkType.cs b/Watrmark.Net CLI/Watermark.Net.CLI.Models/WatermarkType.cs
new file mode 100644
index 0000000..76aed57
--- /dev/null
+++ b/Watrmark.Net CLI/Watermark.Net.CLI.Models/WatermarkType.cs
@@ -0,0 +1,8 @@
+namespace Watrmark.Net_CLI.Watermark.Net.CLI.Models
+{
+ internal enum WatermarkType
+ {
+ Image,
+ Text
+ }
+}
diff --git a/Watrmark.Net CLI/Watrmark.Net CLI.csproj b/Watrmark.Net CLI/Watrmark.Net CLI.csproj
new file mode 100644
index 0000000..d3347d5
--- /dev/null
+++ b/Watrmark.Net CLI/Watrmark.Net CLI.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net8.0
+ Watrmark.Net_CLI
+ enable
+ enable
+ Geckon01
+ 0.24.7.1
+
+
+
+
+
+
+
+
+
+
+