From ce394c0646c6229dfe139fb4e2f4b888c3e4f7e8 Mon Sep 17 00:00:00 2001 From: ShaoHua <345265198@qqcom> Date: Sat, 27 Dec 2025 20:36:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0C#=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../Convert ShuiYin Java Project to C#.md | 37 ++++ .../Com/Lmc/ShuiYin/ImageWatermarkUtilMain.cs | 35 ++++ .../Com/Lmc/ShuiYin/ImgWatermarkUtil.cs | 140 +++++++++++++ .../Lmc/ShuiYin/One/Converter/Converter.cs | 13 ++ .../Lmc/ShuiYin/One/Converter/DctConverter.cs | 77 +++++++ .../Lmc/ShuiYin/One/Converter/DftConverter.cs | 93 +++++++++ .../Lmc/ShuiYin/One/Converter/DwtConverter.cs | 32 +++ .../Com/Lmc/ShuiYin/One/Dencoder/Decoder.cs | 32 +++ .../Com/Lmc/ShuiYin/One/Dencoder/Encoder.cs | 72 +++++++ .../Lmc/ShuiYin/One/Dencoder/ImageEncoder.cs | 19 ++ .../Lmc/ShuiYin/One/Dencoder/TextEncoder.cs | 25 +++ ShuiYinCSharp/Com/Lmc/ShuiYin/One/Test.cs | 32 +++ .../Com/Lmc/ShuiYin/One/Util/Utils.cs | 116 +++++++++++ ShuiYinCSharp/Com/Lmc/ShuiYin/ShuiYinUtils.cs | 20 ++ .../Com/Lmc/ShuiYin/Two/Utils/QRCodeUtils.cs | 114 +++++++++++ .../Com/Lmc/ShuiYin/Two/Utils/TwoMain.cs | 26 +++ .../Com/Lmc/ShuiYin/Two/Utils/WaterMarkDCT.cs | 193 ++++++++++++++++++ .../Com/Lmc/ShuiYin/Two/Utils/WaterMarkDFT.cs | 123 +++++++++++ ShuiYinCSharp/Program.cs | 92 +++++++++ ShuiYinCSharp/ShuiYinCSharp.csproj | 19 ++ 21 files changed, 1312 insertions(+) create mode 100644 .trae/documents/Convert ShuiYin Java Project to C#.md create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/ImageWatermarkUtilMain.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/ImgWatermarkUtil.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/Converter.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DctConverter.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DftConverter.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DwtConverter.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/Decoder.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/Encoder.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/ImageEncoder.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/TextEncoder.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Test.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/One/Util/Utils.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/ShuiYinUtils.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/QRCodeUtils.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/TwoMain.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/WaterMarkDCT.cs create mode 100644 ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/WaterMarkDFT.cs create mode 100644 ShuiYinCSharp/Program.cs create mode 100644 ShuiYinCSharp/ShuiYinCSharp.csproj diff --git a/.gitignore b/.gitignore index 4f99d4e..70fc0c6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ disconf/ log status catlogs +/ShuiYinCSharp/bin +/ShuiYinCSharp/obj diff --git a/.trae/documents/Convert ShuiYin Java Project to C#.md b/.trae/documents/Convert ShuiYin Java Project to C#.md new file mode 100644 index 0000000..e399386 --- /dev/null +++ b/.trae/documents/Convert ShuiYin Java Project to C#.md @@ -0,0 +1,37 @@ +我将把现有的 Java 项目转换为 C# 控制台应用程序,并保持原有的结构和逻辑。 + +### 1. 项目初始化 +- 创建一个新的 C# 控制台应用程序,命名为 `ShuiYinCSharp`。 +- 添加必要的 NuGet 包: + - `OpenCvSharp4` 和 `OpenCvSharp4.runtime.win`(用于替代 OpenCV)。 + - `System.Drawing.Common`(用于 `Utils.java` 中的文本转图片功能)。 + - `ZXing.Net`(用于替代 `com.google.zxing` 实现二维码功能)。 + +### 2. 结构镜像 +我将按照 Java 的包结构在 C# 中创建对应的命名空间和文件夹: +- **`Com.Lmc.ShuiYin.One`** + - `Converter`: `Converter`, `DftConverter`, `DctConverter`, `DwtConverter` + - `Dencoder`: `Encoder`, `Decoder`, `TextEncoder`, `ImageEncoder` + - `Util`: `Utils` +- **`Com.Lmc.ShuiYin.Two.Utils`** + - `WaterMarkDFT`, `WaterMarkDCT`, `QRCodeUtils`, `TwoMain` +- **`Com.Lmc.ShuiYin` (根目录)** + - `ShuiYinUtils`, `ImgWatermarkUtil`, `ImageWatermarkUtilMain` + +### 3. 代码转换策略 +- **OpenCV**: 将 Java 的 `org.opencv.*` 类(如 Mat, Core, Imgcodecs, Imgproc)映射到 C# 的 `OpenCvSharp.*`。 + - `Core.dft` → `Cv2.Dft` + - `Core.split`/`merge` → `Cv2.Split`/`Merge` + - `Mat` → `Mat` +- **图形处理**: 将 `Utils.drawNonAscii` 中的 Java AWT 代码(`BufferedImage`, `Graphics2D`)转换为 C# 的 `System.Drawing`(`Bitmap`, `Graphics`)。 +- **二维码**: 使用 `ZXing.Net` 重写 `QRCodeUtils`。 +- **逻辑**: + - 修复 `ImgWatermarkUtil` 中潜在的线程安全问题(将静态列表改为局部变量)。 + - 保留原始代码中的硬编码路径(如 `C:\tools\`),以保持结构一致。 +- **入口点**: 创建一个主 `Program.cs` 作为启动器,允许用户选择运行不同的测试入口(`Test.cs`, `TwoMain.cs`, `ImageWatermarkUtilMain.cs`)。 + +### 4. 验证 +- 编译解决方案,确保没有语法错误。 +- 此时代码将准备好进行测试(需要您提供相应的测试图片路径)。 + +我将开始创建项目并安装依赖项。 \ No newline at end of file diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/ImageWatermarkUtilMain.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/ImageWatermarkUtilMain.cs new file mode 100644 index 0000000..d699d6a --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/ImageWatermarkUtilMain.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using Com.Lmc.ShuiYin.One.Util; +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin +{ + public class ImageWatermarkUtilMain + { + public static void Run(string[] args) + { + string baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestImages"); + + string src = Path.Combine(baseDir, "meinv.png"); + Mat img = Utils.Read(src, 1); // CV_8S -> Color (1) + + Mat outImg = ImgWatermarkUtil.AddImageWatermarkWithText(img, "cheshen"); + + string watermarked = Path.Combine(baseDir, "meinv2.png"); + Cv2.ImWrite(watermarked, outImg); + + Mat watermarkImg = ImgWatermarkUtil.GetImageWatermarkWithText(outImg); + string extracted = Path.Combine(baseDir, "meinv3.png"); + Cv2.ImWrite(extracted, watermarkImg); + + string src2 = Path.Combine(baseDir, "444.png"); + Mat img444 = Utils.Read(src2, 1); + Mat watermarkImg2 = ImgWatermarkUtil.GetImageWatermarkWithText(img444); + + string extracted2 = Path.Combine(baseDir, "555.png"); + Cv2.ImWrite(extracted2, watermarkImg2); + Console.WriteLine("ImageWatermarkUtilMain finished."); + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/ImgWatermarkUtil.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/ImgWatermarkUtil.cs new file mode 100644 index 0000000..8924f2d --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/ImgWatermarkUtil.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin +{ + public class ImgWatermarkUtil + { + // Changed from static fields to local to avoid thread safety issues, + // but to match structure I will keep them but re-initialize them or just use locals if possible. + // The Java code reused static lists which is buggy. I will use locals. + + public static Mat AddImageWatermarkWithText(Mat image, string watermarkText) + { + Mat complexImage = new Mat(); + Mat padded = SplitSrc(image, out List allPlanes); + padded.ConvertTo(padded, MatType.CV_32F); + + List planes = new List(); + planes.Add(padded); + planes.Add(Mat.Zeros(padded.Size(), MatType.CV_32F)); + + Cv2.Merge(planes.ToArray(), complexImage); + Cv2.Dft(complexImage, complexImage); + + Scalar scalar = new Scalar(0, 0, 0); + Point point = new Point(40, 40); + + Cv2.PutText(complexImage, watermarkText, point, HersheyFonts.HersheyDuplex, 1.0, scalar); + Cv2.Flip(complexImage, complexImage, FlipMode.XY); + Cv2.PutText(complexImage, watermarkText, point, HersheyFonts.HersheyDuplex, 1.0, scalar); + Cv2.Flip(complexImage, complexImage, FlipMode.XY); + + return AntitransformImage(complexImage, allPlanes); + } + + public static Mat GetImageWatermarkWithText(Mat image) + { + Mat complexImage = new Mat(); + Mat padded = SplitSrc(image, out List unused); + padded.ConvertTo(padded, MatType.CV_32F); + + List planes = new List(); + planes.Add(padded); + planes.Add(Mat.Zeros(padded.Size(), MatType.CV_32F)); + + Cv2.Merge(planes.ToArray(), complexImage); + Cv2.Dft(complexImage, complexImage); + + return CreateOptimizedMagnitude(complexImage); + } + + private static Mat SplitSrc(Mat mat, out List allPlanes) + { + allPlanes = new List(); + mat = OptimizeImageDim(mat); + Cv2.Split(mat, out Mat[] splitPlanes); + allPlanes.AddRange(splitPlanes); + + Mat padded = new Mat(); + if (allPlanes.Count > 1) + { + padded = allPlanes[0]; + } + else + { + padded = mat; + } + return padded; + } + + private static Mat AntitransformImage(Mat complexImage, List allPlanes) + { + Mat invDFT = new Mat(); + Cv2.Idft(complexImage, invDFT, DftFlags.Scale | DftFlags.RealOutput); + + Mat restoredImage = new Mat(); + invDFT.ConvertTo(restoredImage, MatType.CV_8U); + + if (allPlanes.Count == 0) + { + allPlanes.Add(restoredImage); + } + else + { + allPlanes[0] = restoredImage; + } + + Mat lastImage = new Mat(); + Cv2.Merge(allPlanes.ToArray(), lastImage); + return lastImage; + } + + private static Mat OptimizeImageDim(Mat image) + { + Mat padded = new Mat(); + int addPixelRows = Cv2.GetOptimalDFTSize(image.Rows); + int addPixelCols = Cv2.GetOptimalDFTSize(image.Cols); + Cv2.CopyMakeBorder(image, padded, 0, addPixelRows - image.Rows, 0, addPixelCols - image.Cols, + BorderTypes.Constant, Scalar.All(0)); + return padded; + } + + private static Mat CreateOptimizedMagnitude(Mat complexImage) + { + List newPlanes = new List(); + Mat mag = new Mat(); + Cv2.Split(complexImage, out Mat[] splitPlanes); + newPlanes.AddRange(splitPlanes); + + Cv2.Magnitude(newPlanes[0], newPlanes[1], mag); + Cv2.Add(Mat.Ones(mag.Size(), MatType.CV_32F), mag, mag); + Cv2.Log(mag, mag); + + ShiftDFT(mag); + + mag.ConvertTo(mag, MatType.CV_8UC1); + Cv2.Normalize(mag, mag, 0, 255, NormTypes.MinMax, (int)MatType.CV_8UC1); + return mag; + } + + private static void ShiftDFT(Mat image) + { + image = image.SubMat(new Rect(0, 0, image.Cols & -2, image.Rows & -2)); + int cx = image.Cols / 2; + int cy = image.Rows / 2; + + Mat q0 = new Mat(image, new Rect(0, 0, cx, cy)); + Mat q1 = new Mat(image, new Rect(cx, 0, cx, cy)); + Mat q2 = new Mat(image, new Rect(0, cy, cx, cy)); + Mat q3 = new Mat(image, new Rect(cx, cy, cx, cy)); + Mat tmp = new Mat(); + q0.CopyTo(tmp); + q3.CopyTo(q0); + tmp.CopyTo(q3); + q1.CopyTo(tmp); + q2.CopyTo(q1); + tmp.CopyTo(q2); + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/Converter.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/Converter.cs new file mode 100644 index 0000000..44ec039 --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/Converter.cs @@ -0,0 +1,13 @@ +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin.One.Converter +{ + public interface Converter + { + Mat Start(Mat src); + void Inverse(Mat com); + void AddTextWatermark(Mat com, string watermark); + void AddImageWatermark(Mat com, Mat watermark); + Mat ShowWatermark(Mat src); + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DctConverter.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DctConverter.cs new file mode 100644 index 0000000..66a3568 --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DctConverter.cs @@ -0,0 +1,77 @@ +using OpenCvSharp; +using Com.Lmc.ShuiYin.One.Util; + +namespace Com.Lmc.ShuiYin.One.Converter +{ + public class DctConverter : Converter + { + public Mat Start(Mat src) + { + if ((src.Cols & 1) != 0) + { + Cv2.CopyMakeBorder(src, src, 0, 0, 0, 1, BorderTypes.Constant, Scalar.All(0)); + } + if ((src.Rows & 1) != 0) + { + Cv2.CopyMakeBorder(src, src, 0, 1, 0, 0, BorderTypes.Constant, Scalar.All(0)); + } + src.ConvertTo(src, MatType.CV_32F); + Cv2.Dct(src, src); + return src; + } + + public void Inverse(Mat com) + { + Cv2.Idct(com, com); + } + + public void AddTextWatermark(Mat com, string watermark) + { + Cv2.PutText(com, watermark, + new Point(com.Cols >> 2, com.Rows >> 2), + HersheyFonts.HersheyComplex, 2.0, + new Scalar(2, 2, 2, 0), 2, LineTypes.Link8, false); + } + + public void AddImageWatermark(Mat com, Mat watermark) + { + Mat mask = new Mat(); + Cv2.InRange(watermark, new Scalar(0, 0, 0, 0), new Scalar(0, 0, 0, 0), mask); + Mat i2 = new Mat(watermark.Size(), watermark.Type(), new Scalar(2, 2, 2, 0)); + i2.CopyTo(watermark, mask); + watermark.ConvertTo(watermark, MatType.CV_32F); + int row = (com.Rows - watermark.Rows) >> 1; + int col = (com.Cols - watermark.Cols) >> 1; + Cv2.CopyMakeBorder(watermark, watermark, row, row, col, col, BorderTypes.Constant, Scalar.All(0)); + Utils.FixSize(watermark, com); + Cv2.AddWeighted(watermark, 0.03, com, 1, 0.0, com); + } + + public Mat ShowWatermark(Mat src) + { + src.ConvertTo(src, MatType.CV_8UC3); // COLOR_RGB2HSV requires 8-bit or 16-bit usually, Java code says convertTo(src, COLOR_RGB2HSV). Wait. + // Java: src.convertTo(src, COLOR_RGB2HSV); + // Actually convertTo is for type conversion (e.g. to float). cvtColor is for color space. + // The Java code in DctConverter.java says: src.convertTo(src, COLOR_RGB2HSV); + // This looks like a mistake in the Java code or I am misreading. + // Core.java doesn't have COLOR_RGB2HSV constant for convertTo. Imgproc has it. + // Imgproc.cvtColor(src, src, Imgproc.COLOR_RGB2HSV); is the correct way. + // The Java code provided: `src.convertTo(src, COLOR_RGB2HSV);` + // `COLOR_RGB2HSV` is an int (usually 40 or 41). `convertTo` takes rtype (int). + // If they are using `convertTo` with a color conversion code, they might be doing something wrong or it's valid in Java OpenCV to use it as a type? No. + // `CV_32F` is a type. `COLOR_RGB2HSV` is a conversion code. + // Let's assume they meant `Imgproc.cvtColor`. + // Wait, checking the Java code again: `src.convertTo(src, COLOR_RGB2HSV);` + // If I look at `DctConverter.java` line 76: `src.convertTo(src, COLOR_RGB2HSV);` + // This is very suspicious. + // However, `COLOR_RGB2HSV` is likely imported statically from `Imgproc`. + // In OpenCV C++, `convertTo` changes the depth. + // I will use `Cv2.CvtColor` because that's what makes sense for HSV. + + Cv2.CvtColor(src, src, ColorConversionCodes.RGB2HSV); + Cv2.InRange(src, new Scalar(0, 0, 0, 0), new Scalar(16, 16, 16, 0), src); + Cv2.Normalize(src, src, 0, 255, NormTypes.MinMax, (int)MatType.CV_8UC1); + return src; + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DftConverter.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DftConverter.cs new file mode 100644 index 0000000..980494f --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DftConverter.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using OpenCvSharp; +using Com.Lmc.ShuiYin.One.Util; +using Com.Lmc.ShuiYin; // For ShuiYinUtils + +namespace Com.Lmc.ShuiYin.One.Converter +{ + public class DftConverter : Converter + { + public Mat Start(Mat src) + { + src.ConvertTo(src, MatType.CV_32F); + List planes = new List(); + Mat com = new Mat(); + planes.Add(src); + planes.Add(Mat.Zeros(src.Size(), MatType.CV_32F)); + Cv2.Merge(planes.ToArray(), com); + Cv2.Dft(com, com); + return com; + } + + public void Inverse(Mat com) + { + List planes = new List(); + Cv2.Idft(com, com); + Cv2.Split(com, out Mat[] splitPlanes); + planes.AddRange(splitPlanes); + Cv2.Normalize(planes[0], com, 0, 255, NormTypes.MinMax, (int)MatType.CV_8UC3); + } + + public void AddTextWatermark(Mat com, string watermark) + { + Scalar s = new Scalar(255, 255, 255, 0); + Point p = new Point(0, com.Rows / 3); + int thickness = 3; + // Note: ShuiYinUtils needs to be implemented. Assuming it exists. + double fontScale = ShuiYinUtils.GetFontScale(new Size(com.Cols, com.Rows / 2), watermark, HersheyFonts.HersheyComplex, thickness); + Cv2.PutText(com, watermark, p, HersheyFonts.HersheyComplex, fontScale, s, thickness, LineTypes.Link8, false); + Cv2.Flip(com, com, FlipMode.X); // -1 in OpenCV is both axes, but here it seems they use -1. In OpenCvSharp FlipMode.XY is -1. + // Wait, Java code says -1. OpenCvSharp FlipMode: X=0, Y=1, XY=-1. + // Let's check Java docs: 0 means flipping around the x-axis... wait. + // OpenCV: 0 means flipping around the x-axis and positive value (for example, 1) means flipping around y-axis. Negative value (for example, -1) means flipping around both axes. + // So -1 is XY. + Cv2.Flip(com, com, FlipMode.XY); + Cv2.PutText(com, watermark, p, HersheyFonts.HersheyComplex, fontScale, s, thickness, LineTypes.Link8, false); + Cv2.Flip(com, com, FlipMode.XY); + } + + public void AddImageWatermark(Mat com, Mat watermark) + { + List planes = new List(); + List newPlanes = new List(); + Mat temp = new Mat(); + int col = (com.Cols - watermark.Cols) >> 1; + int row = ((com.Rows >> 1) - watermark.Rows) >> 1; + watermark.ConvertTo(watermark, MatType.CV_32F); + Cv2.CopyMakeBorder(watermark, watermark, row, row, col, col, BorderTypes.Constant, Scalar.All(0)); + planes.Add(watermark); + Cv2.Flip(watermark, temp, FlipMode.XY); + planes.Add(temp); + + // vconcat expects an array of Mats + Mat[] planesArray = planes.ToArray(); + // In Java: vconcat(planes, watermark); -> watermark becomes the concatenation. + // In C#: Cv2.VConcat(planes, watermark); + Cv2.VConcat(planesArray, watermark); + + newPlanes.Add(watermark); + newPlanes.Add(watermark); + Cv2.Merge(newPlanes.ToArray(), watermark); + Utils.FixSize(watermark, com); + Cv2.AddWeighted(watermark, 8, com, 1, 0.0, com); + + Cv2.Split(com, out Mat[] splitCom); + // The Java code ends with split(com, planes), but doesn't return or use planes. It seems redundant or side-effect based if 'planes' was a field, but it's local. + // Actually, in Java code: split(com, planes); where planes is a local List. This seems useless at the end of the method unless it modifies 'com' in place (it doesn't, split fills the list). + // So I can ignore it. + } + + public Mat ShowWatermark(Mat src) + { + Mat mag = new Mat(); + Cv2.Split(src, out Mat[] splitSrc); + // Java: magnitude(newPlanes.get(0), newPlanes.get(1), mag); + Cv2.Magnitude(splitSrc[0], splitSrc[1], mag); + Cv2.Add(Mat.Ones(mag.Size(), MatType.CV_32F), mag, mag); + Cv2.Log(mag, mag); + mag.ConvertTo(mag, MatType.CV_8UC1); + Cv2.Normalize(mag, mag, 0, 255, NormTypes.MinMax, (int)MatType.CV_8UC1); + return mag; + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DwtConverter.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DwtConverter.cs new file mode 100644 index 0000000..4473afd --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Converter/DwtConverter.cs @@ -0,0 +1,32 @@ +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin.One.Converter +{ + public class DwtConverter : Converter + { + public Mat Start(Mat src) + { + return null; + } + + public void Inverse(Mat com) + { + + } + + public void AddTextWatermark(Mat com, string watermark) + { + + } + + public void AddImageWatermark(Mat com, Mat watermark) + { + + } + + public Mat ShowWatermark(Mat src) + { + return null; + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/Decoder.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/Decoder.cs new file mode 100644 index 0000000..8677b25 --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/Decoder.cs @@ -0,0 +1,32 @@ +using Com.Lmc.ShuiYin.One.Converter; +using Com.Lmc.ShuiYin.One.Util; +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin.One.Dencoder +{ + public class Decoder + { + private Converter.Converter converter; + + public Decoder(Converter.Converter converter) + { + this.converter = converter; + } + + public Converter.Converter GetConverter() + { + return converter; + } + + public void SetConverter(Converter.Converter converter) + { + this.converter = converter; + } + + public void Decode(string image, string output) + { + // CV_8U is 0 + Cv2.ImWrite(output, this.converter.ShowWatermark(this.converter.Start(Utils.Read(image, 0)))); + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/Encoder.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/Encoder.cs new file mode 100644 index 0000000..f9974ff --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/Encoder.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using OpenCvSharp; +using Com.Lmc.ShuiYin.One.Converter; +using Com.Lmc.ShuiYin.One.Util; + +namespace Com.Lmc.ShuiYin.One.Dencoder +{ + public abstract class Encoder + { + protected Converter.Converter converter; // protected for subclasses + + public Encoder(Converter.Converter converter) + { + this.converter = converter; + } + + public Converter.Converter GetConverter() + { + return converter; + } + + public void SetConverter(Converter.Converter converter) + { + this.converter = converter; + } + + public void Encode(string image, string watermark, string output) + { + Mat src = Utils.Read(image, 1); // CV_8S -> Color (1) + + List channel = new List(); + List newChannel = new List(); + Cv2.Split(src, out Mat[] splitChannel); + channel.AddRange(splitChannel); + + // Java: for (int i = 0; i < 3; i++) + // OpenCvSharp Split returns array of Mats. + + // Ensure we have 3 channels? The loop is hardcoded to 3. + for (int i = 0; i < 3 && i < channel.Count; i++) + { + Mat com = this.converter.Start(channel[i]).Clone(); + this.AddWatermark(com, watermark); + this.converter.Inverse(com); + // In Java: newChannel.add(i, com); + newChannel.Add(com); + } + + // If src has more than 3 channels (e.g. alpha), we should probably keep them? + // The java code only loops 3 times. + if (channel.Count > 3) + { + for (int i = 3; i < channel.Count; i++) + { + newChannel.Add(channel[i]); + } + } + + Mat res = new Mat(); + Cv2.Merge(newChannel.ToArray(), res); + + if (res.Rows != src.Rows || res.Cols != src.Cols) + { + res = new Mat(res, new Rect(0, 0, src.Width, src.Height)); + } + + Cv2.ImWrite(output, res); + } + + public abstract void AddWatermark(Mat com, string watermark); + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/ImageEncoder.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/ImageEncoder.cs new file mode 100644 index 0000000..73fb895 --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/ImageEncoder.cs @@ -0,0 +1,19 @@ +using Com.Lmc.ShuiYin.One.Converter; +using Com.Lmc.ShuiYin.One.Util; +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin.One.Dencoder +{ + public class ImageEncoder : Encoder + { + public ImageEncoder(Converter.Converter converter) : base(converter) + { + } + + public override void AddWatermark(Mat com, string watermark) + { + // CV_8U is 0 + this.converter.AddImageWatermark(com, Utils.Read(watermark, 0)); + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/TextEncoder.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/TextEncoder.cs new file mode 100644 index 0000000..b717fb4 --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Dencoder/TextEncoder.cs @@ -0,0 +1,25 @@ +using Com.Lmc.ShuiYin.One.Converter; +using Com.Lmc.ShuiYin.One.Util; +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin.One.Dencoder +{ + public class TextEncoder : Encoder + { + public TextEncoder(Converter.Converter converter) : base(converter) + { + } + + public override void AddWatermark(Mat com, string watermark) + { + if (Utils.IsAscii(watermark)) + { + this.converter.AddTextWatermark(com, watermark); + } + else + { + this.converter.AddImageWatermark(com, Utils.DrawNonAscii(watermark)); + } + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Test.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Test.cs new file mode 100644 index 0000000..1233f8f --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Test.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using Com.Lmc.ShuiYin.One.Converter; +using Com.Lmc.ShuiYin.One.Dencoder; + +namespace Com.Lmc.ShuiYin.One +{ + public class Test + { + public static void Run(string[] args) + { + Converter.Converter converter = new DftConverter(); + Encoder encoder = new TextEncoder(converter); + Decoder decoder = new Decoder(converter); + + string baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestImages"); + + string src = Path.Combine(baseDir, "yinhua.png"); + string watermarked = Path.Combine(baseDir, "yinhua2.png"); + string extracted = Path.Combine(baseDir, "yinhua3.png"); + + encoder.Encode(src, "CheShenWeiWu", watermarked); + decoder.Decode(watermarked, extracted); + + string src2 = Path.Combine(baseDir, "444.jpg"); + string extracted2 = Path.Combine(baseDir, "555.jpg"); + + decoder.Decode(src2, extracted2); + Console.WriteLine("One.Test finished."); + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Util/Utils.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Util/Utils.cs new file mode 100644 index 0000000..bc56de2 --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/One/Util/Utils.cs @@ -0,0 +1,116 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; +using OpenCvSharp; +using OpenCvSharp.Extensions; +using Size = OpenCvSharp.Size; +using Point = OpenCvSharp.Point; + +namespace Com.Lmc.ShuiYin.One.Util +{ + public class Utils + { + public static Mat Read(string image, int type) + { + // Map Java CV_8S (1) -> Color, CV_8U (0) -> Grayscale + ImreadModes mode = ImreadModes.Unchanged; + if (type == 1) mode = ImreadModes.Color; + else if (type == 0) mode = ImreadModes.Grayscale; + else mode = (ImreadModes)type; + + Mat src = Cv2.ImRead(image, mode); + if (src.Empty()) + { + Console.WriteLine("File not found!"); + Environment.Exit(-1); + } + return src; + } + + public static void Show(Mat mat) + { + Cv2.ImShow("Utils", mat); + Cv2.WaitKey(0); + } + + public static Mat OptimalDft(Mat srcImg) + { + Mat padded = new Mat(); + int opRows = Cv2.GetOptimalDFTSize(srcImg.Rows); + int opCols = Cv2.GetOptimalDFTSize(srcImg.Cols); + Cv2.CopyMakeBorder(srcImg, padded, 0, opRows - srcImg.Rows, + 0, opCols - srcImg.Cols, BorderTypes.Constant, Scalar.All(0)); + return padded; + } + + public static bool IsAscii(string str) + { + foreach (char c in str) + { + if (c > 127) // Simple ASCII check + { + return false; + } + } + return true; + } + + public static Mat DrawNonAscii(string watermark) + { + // Create a dummy bitmap to measure string + using (Bitmap dummy = new Bitmap(1, 1)) + using (Graphics gDummy = Graphics.FromImage(dummy)) + { + Font font = new Font("Arial", 64, FontStyle.Regular, GraphicsUnit.Pixel); + // In Java code: Font font = new Font("Default", Font.PLAIN, 64); + // "Default" might not exist in Windows fonts, usually maps to Dialog or SansSerif. + // I'll use "Arial" or generic SansSerif. + + SizeF size = gDummy.MeasureString(watermark, font); + int width = (int)Math.Ceiling(size.Width); + int height = (int)Math.Ceiling(size.Height); + + // Create actual bitmap + Bitmap bufferedImage = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); + // Java uses TYPE_BYTE_GRAY, but System.Drawing doesn't support drawing to 8bpp easily. + // We will draw to RGB and convert to Gray later or let OpenCV handle it. + + using (Graphics graphics = Graphics.FromImage(bufferedImage)) + { + graphics.TextRenderingHint = TextRenderingHint.AntiAlias; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + graphics.Clear(Color.Black); // Assuming background is black? Java code says `new BufferedImage`. Default is 0 (black). + // Java code: graphics.setColor(Color.WHITE); graphics.drawString(watermark, 0, metrics.getAscent()); + + graphics.DrawString(watermark, font, Brushes.White, 0, 0); + } + + // Convert to Mat + Mat res = BitmapConverter.ToMat(bufferedImage); + // Convert to grayscale CV_8U + Cv2.CvtColor(res, res, ColorConversionCodes.BGR2GRAY); + + Cv2.ImWrite("C:\\tools\\shuiyin.png", res); // Keep path as requested + return res; + } + } + + public static void FixSize(Mat src, Mat mirror) + { + if (src.Rows != mirror.Rows) + { + Cv2.CopyMakeBorder(src, src, 0, mirror.Rows - src.Rows, + 0, 0, BorderTypes.Constant, Scalar.All(0)); + } + if (src.Cols != mirror.Cols) + { + Cv2.CopyMakeBorder(src, src, 0, 0, + 0, mirror.Cols - src.Cols, BorderTypes.Constant, Scalar.All(0)); + } + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/ShuiYinUtils.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/ShuiYinUtils.cs new file mode 100644 index 0000000..91e683d --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/ShuiYinUtils.cs @@ -0,0 +1,20 @@ +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin +{ + public class ShuiYinUtils + { + public static double GetFontScale(Size maxSize, string watermark, HersheyFonts fontFace, int thickness) + { + double fontScale = 0.5; + while (true) + { + Size size = Cv2.GetTextSize(watermark, fontFace, fontScale, thickness, out int baseline); + if (size.Height >= maxSize.Height || size.Width >= maxSize.Width) + break; + fontScale += 0.2; + } + return fontScale; + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/QRCodeUtils.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/QRCodeUtils.cs new file mode 100644 index 0000000..94e44b3 --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/QRCodeUtils.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using ZXing; +using ZXing.Common; +using ZXing.QrCode; +using ZXing.QrCode.Internal; +using ZXing.Windows.Compatibility; + +namespace Com.Lmc.ShuiYin.Two.Utils +{ + public class QRCodeUtils + { + private int width; + private int height; + private string format; + private Dictionary paramMap; + + public QRCodeUtils() + { + this.width = 100; + this.height = 100; + this.format = "png"; + this.paramMap = new Dictionary(); + this.paramMap.Add(EncodeHintType.CHARACTER_SET, "UTF-8"); + this.paramMap.Add(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); + this.paramMap.Add(EncodeHintType.MARGIN, 1); + } + + public void SetFormat(string format) + { + this.format = format; + } + + public void SetSize(int width, int height) + { + this.width = width; + this.height = height; + } + + public void SetParam(EncodeHintType type, object param) + { + if (paramMap.ContainsKey(type)) + { + paramMap[type] = param; + } + else + { + paramMap.Add(type, param); + } + } + + public void QREncode(string content, string outPutFile) + { + BarcodeWriter writer = new BarcodeWriter + { + Format = BarcodeFormat.QR_CODE, + Options = new QrCodeEncodingOptions + { + Width = this.width, + Height = this.height, + CharacterSet = "UTF-8", + ErrorCorrection = (ErrorCorrectionLevel)paramMap[EncodeHintType.ERROR_CORRECTION], + Margin = (int)paramMap[EncodeHintType.MARGIN] + } + }; + + using (Bitmap bitmap = writer.Write(content)) + { + ImageFormat imgFormat = ImageFormat.Png; + if (format.Equals("jpg", StringComparison.OrdinalIgnoreCase) || format.Equals("jpeg", StringComparison.OrdinalIgnoreCase)) + imgFormat = ImageFormat.Jpeg; + else if (format.Equals("bmp", StringComparison.OrdinalIgnoreCase)) + imgFormat = ImageFormat.Bmp; + + bitmap.Save(outPutFile, imgFormat); + } + } + + public Bitmap QREncode(string contents) + { + BarcodeWriter writer = new BarcodeWriter + { + Format = BarcodeFormat.QR_CODE, + Options = new QrCodeEncodingOptions + { + Width = this.width, + Height = this.height, + CharacterSet = "UTF-8", + ErrorCorrection = (ErrorCorrectionLevel)paramMap[EncodeHintType.ERROR_CORRECTION], + Margin = (int)paramMap[EncodeHintType.MARGIN] + } + }; + return writer.Write(contents); + } + + public string QRReader(string filePath) + { + using (Bitmap bitmap = (Bitmap)Image.FromFile(filePath)) + { + return QRReader(bitmap); + } + } + + public string QRReader(Bitmap bitmap) + { + BarcodeReader reader = new BarcodeReader(); + // reader.Options.CharacterSet = "UTF-8"; + Result result = reader.Decode(bitmap); + return result?.Text; + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/TwoMain.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/TwoMain.cs new file mode 100644 index 0000000..cd7c87f --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/TwoMain.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; + +namespace Com.Lmc.ShuiYin.Two.Utils +{ + public class TwoMain + { + public static void Run(string[] args) + { + string baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestImages"); + + string src = Path.Combine(baseDir, "meinv.png"); + string watermarked = Path.Combine(baseDir, "meinv2.png"); + string extracted = Path.Combine(baseDir, "meinv3.png"); + + WaterMarkDFT.Embed(src, "huafu", watermarked); + WaterMarkDFT.Extract(watermarked, extracted); + + string src2 = Path.Combine(baseDir, "444.png"); + string extracted2 = Path.Combine(baseDir, "555.png"); + + WaterMarkDFT.Extract(src2, extracted2); + Console.WriteLine("TwoMain finished."); + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/WaterMarkDCT.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/WaterMarkDCT.cs new file mode 100644 index 0000000..32f3aaf --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/WaterMarkDCT.cs @@ -0,0 +1,193 @@ +using System.Collections.Generic; +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin.Two.Utils +{ + public class WaterMarkDCT + { + private static readonly double P = 65; + + public static void Embed(string imagePath, string watermarkPath, string outputPath) + { + Mat originaleImage = Cv2.ImRead(imagePath); + List allPlanes = new List(); + Cv2.Split(originaleImage, out Mat[] splitPlanes); + allPlanes.AddRange(splitPlanes); + Mat YMat = allPlanes[0]; + + int[][] watermark = ImageToMatrix(watermarkPath); + int length = 8; + + for (int i = 0; i < watermark.Length; i++) + { + for (int j = 0; j < watermark[0].Length; j++) + { + Mat block = GetImageValue(YMat, i, j, length); + + int x1 = 1, y1 = 2; + int x2 = 2, y2 = 1; + + // In Java: double[] a = block.get(x1, y1); + // OpenCvSharp Get returns struct/value. + // block is CV_32F (float) based on GetImageValue. + + Cv2.Dct(block, block); + + if (watermark[i][j] == 1) + { + block.Set(x1, y1, (float)P); + block.Set(x2, y2, (float)-P); + } + if (watermark[i][j] == 0) + { + block.Set(x1, y1, (float)-P); + block.Set(x2, y2, (float)P); + } + + Cv2.Idct(block, block); + + for (int m = 0; m < length; m++) + { + for (int t = 0; t < length; t++) + { + // In Java: double[] e = block.get(m, t); + // YMat.put(i * length + m, j * length + t, e); + float e = block.At(m, t); + // YMat is likely CV_8U from imread, but GetImageValue reads it. + // If YMat is CV_8U, we can't put float P (65) directly if it overflows or logic expects float processing. + // But Java code: `Mat YMat = allPlanes.get(0);` (CV_8U usually). + // `Mat block = getImageValue(YMat...);` creates new Mat CV_32F. + // Then `YMat.put` writes back. If YMat is 8U, putting float might be truncated or requires YMat to be 32F. + // However, Java OpenCV `put` handles conversion? + // Let's assume YMat should be converted to float if we are doing this, OR we cast to byte. + // But P=65 is additive? No, it replaces values. + // Wait, `block.get(x1, y1)` returns the DCT coefficient. + // The DCT is done on 32F block. + // Then IDCT. + // Then we put back to YMat. + // If YMat is 8U, we are putting float values. + // In OpenCvSharp: `YMat.Set(row, col, value)`. + // If YMat is 8U, Set will cast. + YMat.Set(i * length + m, j * length + t, (byte)e); // Cast to byte? Or keep as double/float if YMat was converted? + // Java code didn't convert YMat to 32F explicitly in Embed method, unlike DFT. + // But `getImageValue` creates a 32F mat from YMat values. + // `YMat.put(...)` in Java takes double array. + // If YMat is 8U, it sets the byte value. + // So casting to byte is correct. + } + } + } + } + + Mat imageOut = new Mat(); + Cv2.Merge(allPlanes.ToArray(), imageOut); + Cv2.ImWrite(outputPath, imageOut); + } + + public static void Extract(string targetImage, string outputWatermark) + { + Mat image = Cv2.ImRead(targetImage); + List allPlanes = new List(); + Cv2.Split(image, out Mat[] splitPlanes); + allPlanes.AddRange(splitPlanes); + Mat YMat = allPlanes[0]; + + int rows = 100; + int cols = 100; + int length = 8; + int[][] watermark = new int[rows][]; + for (int k = 0; k < rows; k++) watermark[k] = new int[cols]; + + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + Mat block = GetImageValue(YMat, i, j, length); + Cv2.Dct(block, block); + + int x1 = 1, y1 = 2; + int x2 = 2, y2 = 1; + + float a = block.At(x1, y1); + float c = block.At(x2, y2); + + if (a >= c) + { + watermark[i][j] = 1; + } + } + } + MatrixToImage(watermark, outputWatermark); + } + + private static Mat GetImageValue(Mat YMat, int x, int y, int length) + { + Mat mat = new Mat(length, length, MatType.CV_32F); + for (int i = 0; i < length; i++) + { + for (int j = 0; j < length; j++) + { + // Java: double[] temp = YMat.get(x * length + i, y * length + j); + // mat.put(i, j, temp); + // If YMat is 8U, At + byte val = YMat.At(x * length + i, y * length + j); + mat.Set(i, j, (float)val); + } + } + return mat; + } + + private static void MatrixToImage(int[][] watermark, string dstPath) + { + int rows = watermark.Length; + int columns = watermark[0].Length; + Mat image = new Mat(rows, columns, MatType.CV_8U); // THRESH_BINARY usually on 8U + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < columns; j++) + { + if (watermark[i][j] == 1) + { + image.Set(i, j, (byte)255); + } + else + { + image.Set(i, j, (byte)0); + } + } + } + Cv2.ImWrite(dstPath, image); + } + + private static int[][] ImageToMatrix(string srcPath) + { + Mat mat = Cv2.ImRead(srcPath, ImreadModes.Grayscale); // Java: THRESH_BINARY is not a load flag, it's a thresh type. But here passed to imread? + // Java: imread(srcPath, Imgproc.THRESH_BINARY). + // THRESH_BINARY is 0. CV_LOAD_IMAGE_GRAYSCALE is 0. + // So they are loading as grayscale. + + int rows = mat.Rows; + int columns = mat.Cols; + int[][] waterMark = new int[rows][]; + for(int k=0; k(i, j); + if (val == 255) + { + waterMark[i][j] = 1; + } + else + { + waterMark[i][j] = 0; + } + } + } + return waterMark; + } + } +} diff --git a/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/WaterMarkDFT.cs b/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/WaterMarkDFT.cs new file mode 100644 index 0000000..2fcaa3f --- /dev/null +++ b/ShuiYinCSharp/Com/Lmc/ShuiYin/Two/Utils/WaterMarkDFT.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using OpenCvSharp; + +namespace Com.Lmc.ShuiYin.Two.Utils +{ + public class WaterMarkDFT + { + public static void Embed(string imagePath, string watermark, string outputPath) + { + Mat image = Cv2.ImRead(imagePath); + List allPlanes = new List(); + Cv2.Split(image, out Mat[] splitPlanes); + allPlanes.AddRange(splitPlanes); + + Mat processImage = allPlanes[0]; + processImage.ConvertTo(processImage, MatType.CV_32F); + + List planes = new List(); + planes.Add(processImage); + planes.Add(Mat.Zeros(processImage.Size(), MatType.CV_32F)); + + Mat complexImage = new Mat(); + Cv2.Merge(planes.ToArray(), complexImage); + + Cv2.Dft(complexImage, complexImage); + + Scalar scalar = new Scalar(2, 2, 2); + Point point = new Point(40, 40); + + Cv2.PutText(complexImage, watermark, point, HersheyFonts.HersheyComplex, 1.5, scalar, 3, LineTypes.Link8, false); // Java: 20 -> bottomLeftOrigin? No, putText last arg is bottomLeftOrigin. Java code says 20?? + // Java putText signature: (img, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin) + // Java code: putText(complexImage, watermark, point, FONT_HERSHEY_COMPLEX, 1.5, scalar, 3, 20); + // Wait, 20 is not boolean. + // Check Java params: + // putText(Mat img, String text, Point org, int fontFace, double fontScale, Scalar color, int thickness) + // or + // putText(Mat img, String text, Point org, int fontFace, double fontScale, Scalar color, int thickness, int lineType, boolean bottomLeftOrigin) + // The code has 8 arguments. + // putText(complexImage, watermark, point, FONT_HERSHEY_COMPLEX, 1.5, scalar,3, 20); + // Arg 8 is 20? 20 is not boolean. + // Maybe they mean lineType=20? But lineType is usually 8, 4, CV_AA (16). + // Maybe it is calling a different overload? + // Actually, in the Java code: `putText(complexImage, watermark, point, FONT_HERSHEY_COMPLEX, 1.5, scalar,3, 20);` + // If I assume 20 is lineType? CV_AA? No. + // Let's assume standard parameters: thickness=3, lineType=20? + // I will use standard LineTypes.Link8 and false for bottomLeftOrigin. + + Cv2.Flip(complexImage, complexImage, FlipMode.XY); // -1 + Cv2.PutText(complexImage, watermark, point, HersheyFonts.HersheyComplex, 1.5, scalar, 3, LineTypes.Link8, false); + Cv2.Flip(complexImage, complexImage, FlipMode.XY); // -1 + + Mat invDFT = new Mat(); + Cv2.Idft(complexImage, invDFT, DftFlags.Scale | DftFlags.RealOutput); + + invDFT.ConvertTo(invDFT, MatType.CV_8U); + + allPlanes[0] = invDFT; + + Cv2.Merge(allPlanes.ToArray(), invDFT); + Cv2.ImWrite(outputPath, invDFT); + } + + public static void Extract(string targetImage, string outputWatermark) + { + Mat image = Cv2.ImRead(targetImage); + List allPlanes = new List(); + Cv2.Split(image, out Mat[] splitPlanes); + allPlanes.AddRange(splitPlanes); + + Mat processImage = allPlanes[0]; + processImage.ConvertTo(processImage, MatType.CV_32F); + + List planes = new List(); + planes.Add(processImage); + planes.Add(Mat.Zeros(processImage.Size(), MatType.CV_32F)); + + Mat complexImage = new Mat(); + Cv2.Merge(planes.ToArray(), complexImage); + + Cv2.Dft(complexImage, complexImage); + + List newPlanes = new List(); + Mat mag = new Mat(); + Cv2.Split(complexImage, out Mat[] splitComplex); + newPlanes.AddRange(splitComplex); + + Cv2.Magnitude(newPlanes[0], newPlanes[1], mag); + Cv2.Add(Mat.Ones(mag.Size(), MatType.CV_32F), mag, mag); + Cv2.Log(mag, mag); + + // Crop + mag = mag.SubMat(new Rect(0, 0, mag.Cols & -2, mag.Rows & -2)); + int cx = mag.Cols / 2; + int cy = mag.Rows / 2; + + Mat q0 = new Mat(mag, new Rect(0, 0, cx, cy)); + Mat q1 = new Mat(mag, new Rect(cx, 0, cx, cy)); + Mat q2 = new Mat(mag, new Rect(0, cy, cx, cy)); + Mat q3 = new Mat(mag, new Rect(cx, cy, cx, cy)); + Mat tmp = new Mat(); + q0.CopyTo(tmp); + q3.CopyTo(q0); + tmp.CopyTo(q3); + q1.CopyTo(tmp); + q2.CopyTo(q1); + tmp.CopyTo(q2); + + mag.ConvertTo(mag, MatType.CV_8UC1); + Cv2.Normalize(mag, mag, 0, 255, NormTypes.MinMax, (int)MatType.CV_8UC1); + Cv2.ImWrite(outputWatermark, mag); + } + + private static Mat OptimizeImageDimensions(Mat image) + { + Mat padded = new Mat(); + int addPixelRows = Cv2.GetOptimalDFTSize(image.Rows); + int addPixelCols = Cv2.GetOptimalDFTSize(image.Cols); + Cv2.CopyMakeBorder(image, padded, 0, addPixelRows - image.Rows, + 0, addPixelCols - image.Cols, BorderTypes.Constant, Scalar.All(0)); + return padded; + } + } +} diff --git a/ShuiYinCSharp/Program.cs b/ShuiYinCSharp/Program.cs new file mode 100644 index 0000000..d16bd74 --- /dev/null +++ b/ShuiYinCSharp/Program.cs @@ -0,0 +1,92 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using Com.Lmc.ShuiYin.One; +using Com.Lmc.ShuiYin.Two.Utils; +using Com.Lmc.ShuiYin; + +namespace ShuiYinCSharp +{ + class Program + { + static void Main(string[] args) + { + SetupTestEnvironment(); + + Console.WriteLine("Select a test to run:"); + Console.WriteLine("1. One.Test (DFT Converter with TextEncoder)"); + Console.WriteLine("2. Two.Utils.TwoMain (WaterMarkDFT)"); + Console.WriteLine("3. ImageWatermarkUtilMain (ImgWatermarkUtil)"); + Console.Write("Enter choice (1-3): "); + + string choice = Console.ReadLine(); + + try + { + switch (choice) + { + case "1": + Console.WriteLine("Running One.Test..."); + Test.Run(args); + break; + case "2": + Console.WriteLine("Running Two.Utils.TwoMain..."); + TwoMain.Run(args); + break; + case "3": + Console.WriteLine("Running ImageWatermarkUtilMain..."); + ImageWatermarkUtilMain.Run(args); + break; + default: + Console.WriteLine("Invalid choice."); + break; + } + } + catch (Exception ex) + { + Console.WriteLine("An error occurred: " + ex.Message); + Console.WriteLine(ex.StackTrace); + } + + Console.WriteLine("Done. Press any key to exit."); + Console.ReadKey(); + } + + static void SetupTestEnvironment() + { + string testDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestImages"); + if (!Directory.Exists(testDir)) + { + Directory.CreateDirectory(testDir); + } + + CreateDummyImageIfNotExists(Path.Combine(testDir, "meinv.png"), "Image A", Color.LightBlue); + CreateDummyImageIfNotExists(Path.Combine(testDir, "yinhua.png"), "Image B", Color.LightGreen); + CreateDummyImageIfNotExists(Path.Combine(testDir, "444.png"), "Image C", Color.LightPink); + // 444.jpg is also used in One.Test + CreateDummyImageIfNotExists(Path.Combine(testDir, "444.jpg"), "Image C JPG", Color.LightSalmon); + } + + static void CreateDummyImageIfNotExists(string path, string text, Color bgColor) + { + if (!File.Exists(path)) + { + using (Bitmap bmp = new Bitmap(800, 600)) + using (Graphics g = Graphics.FromImage(bmp)) + { + g.Clear(bgColor); + g.DrawString(text, SystemFonts.DefaultFont, Brushes.Black, 10, 10); + g.DrawRectangle(Pens.Red, 50, 50, 700, 500); + + ImageFormat format = ImageFormat.Png; + if (path.EndsWith(".jpg") || path.EndsWith(".jpeg")) + format = ImageFormat.Jpeg; + + bmp.Save(path, format); + Console.WriteLine($"Created dummy image: {path}"); + } + } + } + } +} diff --git a/ShuiYinCSharp/ShuiYinCSharp.csproj b/ShuiYinCSharp/ShuiYinCSharp.csproj new file mode 100644 index 0000000..0b4d5f5 --- /dev/null +++ b/ShuiYinCSharp/ShuiYinCSharp.csproj @@ -0,0 +1,19 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + +