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