浏览代码

1. 全面移除System.Drawing,使用ImageSharp
2. 新增字符串SimHash算法
3. 新增图片相似对比算法

懒得勤快 3 年之前
父节点
当前提交
f45e4bd536

+ 10 - 2
Masuit.Tools.Abstractions/Extensions/BaseType/StringExtensions.cs

@@ -720,6 +720,8 @@ $", RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOption
             return isPatnumTrue;
         }
 
+        #endregion 权威校验中国专利申请号/专利号
+
         /// <summary>
         /// 取字符串前{length}个字
         /// </summary>
@@ -730,7 +732,13 @@ $", RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOption
         {
             return s.Length > length ? s.Substring(0, length) : s;
         }
-    }
 
-    #endregion 权威校验中国专利申请号/专利号
+        /// <summary>
+        /// 对比字符串的汉明距离
+        /// </summary>
+        /// <param name="this"></param>
+        /// <param name="that"></param>
+        /// <returns></returns>
+        public static int HammingDistance(this string @this, string that) => new SimHash(@this).HammingDistance(new SimHash(that));
+    }
 }

+ 3 - 9
Masuit.Tools.Abstractions/Masuit.Tools.Abstractions.csproj

@@ -4,7 +4,7 @@
     <LangVersion>latest</LangVersion>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <CodeAnalysisRuleSet />
-    <Version>2.4.9.4</Version>
+    <Version>2.5</Version>
     <Authors>懒得勤快</Authors>
     <Description>Masuit.Tools基础公共库,包含一些常用的操作类,大都是静态类,加密解密,反射操作,Excel简单导出,权重随机筛选算法,分布式短id,表达式树,linq扩展,文件压缩,多线程下载和FTP客户端,硬件信息,字符串扩展方法,日期时间扩展操作,中国农历,大文件拷贝,图像裁剪,验证码,断点续传,集合扩展等常用封装。</Description>
     <Copyright>懒得勤快,长空X</Copyright>
@@ -45,6 +45,8 @@
     <PackageReference Include="DnsClient" Version="1.6.1" />
     <PackageReference Include="HtmlSanitizer" Version="7.1.512" />
     <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
+    <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
+    <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14" />
     <PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
     <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="4.7.0" />
     <PackageReference Include="System.Management" Version="4.7.0" />
@@ -55,15 +57,11 @@
   <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
     <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
-        <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
-    <PackageReference Include="System.Drawing.Common" Version="4.5.0" />
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1'">
     <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.9" />
-        <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.9" />
-    <PackageReference Include="System.Drawing.Common" Version="5.0.0" />
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'net5'">
@@ -72,9 +70,7 @@
     <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
     <PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
     <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="5.0.1" />
-        <PackageReference Include="Microsoft.Extensions.Http" Version="5.0" />
     <PackageReference Include="System.Management" Version="5.0.0" />
-    <PackageReference Include="System.Drawing.Common" Version="5.0.2" />
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
@@ -83,9 +79,7 @@
     <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
     <PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
     <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="6.0.0" />
-        <PackageReference Include="Microsoft.Extensions.Http" Version="6.0" />
     <PackageReference Include="System.Management" Version="6.0.0" />
-    <PackageReference Include="System.Drawing.Common" Version="6.0.0" />
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">

+ 18 - 0
Masuit.Tools.Abstractions/Media/IImageTransformer.cs

@@ -0,0 +1,18 @@
+using System.IO;
+
+namespace Masuit.Tools.Media;
+
+/// <summary>
+/// 用于在ImageHashes类的哈希函数中实现图像转换操作。
+/// </summary>
+public interface IImageTransformer
+{
+    /// <summary>
+    /// 将给定图像转换为8bit色深通道的灰度图像,并将其调整为给定的宽度和高度。在调整大小操作期间,应忽略纵横比。
+    /// </summary>
+    /// <param name="stream">图像</param>
+    /// <param name="width">给定宽度</param>
+    /// <param name="height">给定高度</param>
+    /// <returns>包含转换图像的8位像素值的字节数组。</returns>
+    byte[] TransformImage(Stream stream, int width, int height);
+}

+ 495 - 0
Masuit.Tools.Abstractions/Media/ImageHasher.cs

@@ -0,0 +1,495 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Masuit.Tools.Media;
+
+public class ImageHasher
+{
+    private readonly IImageTransformer _transformer;
+
+    private float[][] _dctMatrix;
+    private bool _isDctMatrixInitialized;
+    private readonly object _dctMatrixLockObject = new();
+
+    /// <summary>
+    /// 默认使用ImageSharpTransformer初始化实例
+    /// </summary>
+    public ImageHasher()
+    {
+        _transformer = new ImageSharpTransformer();
+    }
+
+    /// <summary>
+    /// 使用给定的IImageTransformer初始化实例
+    /// </summary>
+    /// <param name="transformer">用于图像变换的IImageTransformer的实现类</param>
+    public ImageHasher(IImageTransformer transformer)
+    {
+        _transformer = transformer;
+    }
+
+    /// <summary>
+    /// 使用平均值算法计算图像的64位哈希
+    /// </summary>
+    /// <param name="pathToImage">图片的文件路径</param>
+    /// <returns>64位hash值</returns>
+    public ulong CalculateAverageHash64(string pathToImage)
+    {
+        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
+        return CalculateAverageHash64(stream);
+    }
+
+    /// <summary>
+    /// 使用平均值算法计算图像的64位哈希
+    /// </summary>
+    /// <param name="sourceStream">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong CalculateAverageHash64(Stream sourceStream)
+    {
+        var pixels = _transformer.TransformImage(sourceStream, 8, 8);
+        var average = pixels.Sum(b => b) / 64;
+
+        // 遍历所有像素,如果超过平均值,则将其设置为1,如果低于平均值,则将其设置为0。
+        var hash = 0UL;
+        for (var i = 0; i < 64; i++)
+        {
+            if (pixels[i] > average)
+            {
+                hash |= 1UL << i;
+            }
+        }
+
+        return hash;
+    }
+
+    /// <summary>
+    /// 使用中值算法计算给定图像的64位哈希
+    /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="pathToImage">图片的文件路径</param>
+    /// <returns>64位hash值</returns>
+    public ulong CalculateMedianHash64(string pathToImage)
+    {
+        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
+        return CalculateMedianHash64(stream);
+    }
+
+    /// <summary>
+    /// 使用中值算法计算给定图像的64位哈希
+    /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="sourceStream">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong CalculateMedianHash64(Stream sourceStream)
+    {
+        var pixels = _transformer.TransformImage(sourceStream, 8, 8);
+
+        // 计算中值
+        var pixelList = new List<byte>(pixels);
+        pixelList.Sort();
+
+        // 中间像素中值
+        var median = (byte)((pixelList[31] + pixelList[32]) / 2);
+
+        // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
+        var hash = 0UL;
+        for (var i = 0; i < 64; i++)
+        {
+            if (pixels[i] > median)
+            {
+                hash |= 1UL << i;
+            }
+        }
+
+        return hash;
+    }
+
+    /// <summary>
+    /// 使用中值算法计算给定图像的256位哈希
+    /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="pathToImage">图片的文件路径</param>
+    /// <returns>256位hash值,生成一个4长度的数组返回</returns>
+    public ulong[] CalculateMedianHash256(string pathToImage)
+    {
+        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
+        return CalculateMedianHash256(stream);
+    }
+
+    /// <summary>
+    /// 使用中值算法计算给定图像的256位哈希
+    /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
+    /// </summary>
+    /// <param name="sourceStream">读取到的图片流</param>
+    /// <returns>256位hash值,生成一个4长度的数组返回</returns>
+    public ulong[] CalculateMedianHash256(Stream sourceStream)
+    {
+        var pixels = _transformer.TransformImage(sourceStream, 16, 16);
+
+        // 计算中值
+        var pixelList = new List<byte>(pixels);
+        pixelList.Sort();
+
+        // 中间像素中值
+        var median = (byte)((pixelList[127] + pixelList[128]) / 2);
+
+        // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
+        var hash64 = 0UL;
+        var hash = new ulong[4];
+        for (var i = 0; i < 4; i++)
+        {
+            for (var j = 0; j < 64; j++)
+            {
+                if (pixels[64 * i + j] > median)
+                {
+                    hash64 |= 1UL << j;
+                }
+            }
+            hash[i] = hash64;
+            hash64 = 0UL;
+        }
+
+        return hash;
+    }
+
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="pathToImage">图片的文件路径</param>
+    /// <returns>64位hash值</returns>
+    public ulong CalculateDifferenceHash64(string pathToImage)
+    {
+        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
+        return CalculateDifferenceHash64(stream);
+    }
+
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="sourceStream">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong CalculateDifferenceHash64(Stream sourceStream)
+    {
+        var pixels = _transformer.TransformImage(sourceStream, 9, 8);
+
+        // 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
+        var hash = 0UL;
+        var hashPos = 0;
+        for (var i = 0; i < 8; i++)
+        {
+            var rowStart = i * 9;
+            for (var j = 0; j < 8; j++)
+            {
+                if (pixels[rowStart + j] > pixels[rowStart + j + 1])
+                {
+                    hash |= 1UL << hashPos;
+                }
+
+                hashPos++;
+            }
+        }
+
+        return hash;
+    }
+
+    /// <summary>
+    /// 使用差分哈希算法计算图像的256位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="pathToImage">图片的文件路径</param>
+    /// <returns>256位hash值</returns>
+    public ulong[] CalculateDifferenceHash256(string pathToImage)
+    {
+        using var stream = new FileStream(pathToImage, FileMode.Open, FileAccess.Read);
+        return CalculateDifferenceHash256(stream);
+    }
+
+    /// <summary>
+    /// 使用差分哈希算法计算图像的64位哈希。
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="sourceStream">读取到的图片流</param>
+    /// <returns>256位hash值</returns>
+    public ulong[] CalculateDifferenceHash256(Stream sourceStream)
+    {
+        var pixels = _transformer.TransformImage(sourceStream, 17, 16);
+
+        // 遍历像素,如果左侧像素比右侧像素亮,则将哈希设置为1。
+        var hash = new ulong[4];
+        var hashPos = 0;
+        var hashPart = 0;
+        for (var i = 0; i < 16; i++)
+        {
+            var rowStart = i * 17;
+            for (var j = 0; j < 16; j++)
+            {
+                if (pixels[rowStart + j] > pixels[rowStart + j + 1])
+                {
+                    hash[hashPart] |= 1UL << hashPos;
+                }
+
+                if (hashPos == 63)
+                {
+                    hashPos = 0;
+                    hashPart++;
+                }
+                else
+                {
+                    hashPos++;
+                }
+            }
+        }
+
+        return hash;
+    }
+
+    /// <summary>
+    /// 使用DCT算法计算图像的64位哈希
+    /// </summary>
+    /// <see cref="https://segmentfault.com/a/1190000038308093"/>
+    /// <param name="sourceStream">读取到的图片流</param>
+    /// <returns>64位hash值</returns>
+    public ulong CalculateDctHash(Stream sourceStream)
+    {
+        lock (_dctMatrixLockObject)
+        {
+            if (!_isDctMatrixInitialized)
+            {
+                _dctMatrix = GenerateDctMatrix(32);
+                _isDctMatrixInitialized = true;
+            }
+        }
+
+        var pixels = _transformer.TransformImage(sourceStream, 32, 32);
+
+        // 将像素转换成float类型数组
+        var fPixels = new float[1024];
+        for (var i = 0; i < 1024; i++)
+        {
+            fPixels[i] = pixels[i] / 255.0f;
+        }
+
+        // 计算 dct 矩阵
+        var dctPixels = ComputeDct(fPixels, _dctMatrix);
+
+        // 从矩阵中1,1到8,8获得8x8的区域,忽略最低频率以提高检测
+        var dctHashPixels = new float[64];
+        for (var x = 0; x < 8; x++)
+        {
+            for (var y = 0; y < 8; y++)
+            {
+                dctHashPixels[x + y * 8] = dctPixels[x + 1][y + 1];
+            }
+        }
+
+        // 计算中值
+        var pixelList = new List<float>(dctHashPixels);
+        pixelList.Sort();
+
+        // 中间像素的平均值
+        var median = (pixelList[31] + pixelList[32]) / 2;
+
+        // 遍历所有像素,如果超过中值,则将其设置为1,如果低于中值,则将其设置为0。
+        var hash = 0UL;
+        for (var i = 0; i < 64; i++)
+        {
+            if (dctHashPixels[i] > median)
+            {
+                hash |= 1UL << i;
+            }
+        }
+
+        return hash;
+    }
+
+    /// <summary>
+    /// 使用DCT算法计算图像的64位哈希
+    /// </summary>
+    /// <param name="path">图片的文件路径</param>
+    /// <returns>64位hash值</returns>
+    public ulong CalculateDctHash(string path)
+    {
+        using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
+        return CalculateDctHash(stream);
+    }
+
+    /// <summary>
+    /// 计算图像的DCT矩阵
+    /// </summary>
+    /// <param name="image">用于计算dct的图像</param>
+    /// <param name="dctMatrix">DCT系数矩阵</param>
+    /// <returns>图像的DCT矩阵</returns>
+    private static float[][] ComputeDct(float[] image, float[][] dctMatrix)
+    {
+        // dct矩阵的大小,图像的大小与DCT矩阵相同
+        var size = dctMatrix.GetLength(0);
+
+        // 降图像转换成矩阵
+        var imageMat = new float[size][];
+        for (var i = 0; i < size; i++)
+        {
+            imageMat[i] = new float[size];
+        }
+
+        for (var y = 0; y < size; y++)
+        {
+            for (var x = 0; x < size; x++)
+            {
+                imageMat[y][x] = image[x + y * size];
+            }
+        }
+
+        return Multiply(Multiply(dctMatrix, imageMat), Transpose(dctMatrix));
+    }
+
+    /// <summary>
+    /// 生成DCT系数矩阵
+    /// </summary>
+    /// <param name="size">矩阵的大小</param>
+    /// <returns>DCT系数矩阵</returns>
+    private static float[][] GenerateDctMatrix(int size)
+    {
+        var matrix = new float[size][];
+        for (int i = 0; i < size; i++)
+        {
+            matrix[i] = new float[size];
+        }
+
+        var c1 = Math.Sqrt(2.0f / size);
+
+        for (var j = 0; j < size; j++)
+        {
+            matrix[0][j] = (float)Math.Sqrt(1.0d / size);
+        }
+
+        for (var j = 0; j < size; j++)
+        {
+            for (var i = 1; i < size; i++)
+            {
+                matrix[i][j] = (float)(c1 * Math.Cos(((2 * j + 1) * i * Math.PI) / (2.0d * size)));
+            }
+        }
+
+        return matrix;
+    }
+
+    /// <summary>
+    /// 矩阵的乘法运算
+    /// </summary>
+    /// <param name="a">矩阵a</param>
+    /// <param name="b">矩阵b</param>
+    /// <returns>Result matrix.</returns>
+    private static float[][] Multiply(float[][] a, float[][] b)
+    {
+        var n = a[0].Length;
+        var c = new float[n][];
+        for (var i = 0; i < n; i++)
+        {
+            c[i] = new float[n];
+        }
+
+        for (var i = 0; i < n; i++)
+            for (var k = 0; k < n; k++)
+                for (var j = 0; j < n; j++)
+                    c[i][j] += a[i][k] * b[k][j];
+        return c;
+    }
+
+    /// <summary>
+    /// 矩阵转置
+    /// </summary>
+    /// <param name="mat">待转换的矩阵</param>
+    /// <returns>转换后的矩阵</returns>
+    private static float[][] Transpose(float[][] mat)
+    {
+        var size = mat[0].Length;
+        var transpose = new float[size][];
+
+        for (var i = 0; i < size; i++)
+        {
+            transpose[i] = new float[size];
+            for (var j = 0; j < size; j++)
+                transpose[i][j] = mat[j][i];
+        }
+        return transpose;
+    }
+
+    /// <summary>
+    /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
+    /// </summary>
+    /// <param name="hash1">图像1的hash</param>
+    /// <param name="hash2">图像2的hash</param>
+    /// <returns>相似度范围:[0,1]</returns>
+    public static float CompareHashes(ulong hash1, ulong hash2)
+    {
+        // hash异或运算
+        var hashDifference = hash1 ^ hash2;
+
+        // 计算汉明距离
+        var onesInHash = HammingWeight(hashDifference);
+
+        // 得到相似度
+        return 1.0f - onesInHash / 64.0f;
+    }
+
+    /// <summary>
+    /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
+    /// </summary>
+    /// <param name="hash1">图像1的hash</param>
+    /// <param name="hash2">图像2的hash</param>
+    /// <returns>相似度范围:[0,1]</returns>
+    public static float CompareHashes(ulong[] hash1, ulong[] hash2)
+    {
+        // 检查两个图像的hash长度是否一致
+        if (hash1.Length != hash2.Length)
+        {
+            throw new ArgumentException("hash1 与 hash2长度不匹配");
+        }
+
+        var hashSize = hash1.Length;
+        ulong onesInHash = 0;
+
+        // hash异或运算
+        var hashDifference = new ulong[hashSize];
+        for (var i = 0; i < hashSize; i++)
+        {
+            hashDifference[i] = hash1[i] ^ hash2[i];
+        }
+
+        // 逐个计算汉明距离
+        for (var i = 0; i < hashSize; i++)
+        {
+            onesInHash += HammingWeight(hashDifference[i]);
+        }
+
+        // 得到相似度
+        return 1.0f - onesInHash / (hashSize * 64.0f);
+    }
+
+    /// <summary>
+    /// 计算hash的汉明权重.
+    /// </summary>
+    /// <see cref="http://en.wikipedia.org/wiki/Hamming_weight"/>
+    /// <param name="hash"></param>
+    /// <returns></returns>
+    private static ulong HammingWeight(ulong hash)
+    {
+        hash -= (hash >> 1) & M1;
+        hash = (hash & M2) + ((hash >> 2) & M2);
+        hash = (hash + (hash >> 4)) & M4;
+        var onesInHash = (hash * H01) >> 56;
+
+        return onesInHash;
+    }
+
+    // 汉明距离常量. http://en.wikipedia.org/wiki/Hamming_weight
+    private const ulong M1 = 0x5555555555555555; //binary: 0101...
+
+    private const ulong M2 = 0x3333333333333333; //binary: 00110011..
+    private const ulong M4 = 0x0f0f0f0f0f0f0f0f; //binary:  4 个0,  4 个1 ...
+    private const ulong H01 = 0x0101010101010101; //the sum of 256 to the power of 0,1,2,3...
+}

+ 39 - 0
Masuit.Tools.Abstractions/Media/ImageSharpTransformer.cs

@@ -0,0 +1,39 @@
+using System.IO;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using Image = SixLabors.ImageSharp.Image;
+using Size = SixLabors.ImageSharp.Size;
+
+namespace Masuit.Tools.Media;
+
+/// <summary>
+/// 使用ImageSharp实现IImageTransformer接口进行图像变换
+/// </summary>
+public class ImageSharpTransformer : IImageTransformer
+{
+    public byte[] TransformImage(Stream stream, int width, int height)
+    {
+        using var image = Image.Load<Rgba32>(stream);
+        image.Mutate(x => x.Resize(new ResizeOptions()
+        {
+            Size = new Size
+            {
+                Width = width,
+                Height = height
+            },
+            Mode = ResizeMode.Stretch,
+            Sampler = new BicubicResampler()
+        }).Grayscale());
+        image.DangerousTryGetSinglePixelMemory(out var pixelSpan);
+        var pixelArray = pixelSpan.ToArray();
+        var pixelCount = width * height;
+        var bytes = new byte[pixelCount];
+        for (var i = 0; i < pixelCount; i++)
+        {
+            bytes[i] = pixelArray[i].B;
+        }
+
+        return bytes;
+    }
+}

文件差异内容过多而无法显示
+ 64 - 827
Masuit.Tools.Abstractions/Media/ImageUtilities.cs


+ 92 - 34
Masuit.Tools.Abstractions/Media/ImageWatermarker.cs

@@ -1,7 +1,11 @@
 using System;
-using System.Drawing;
-using System.Drawing.Text;
+using SixLabors.Fonts;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Drawing.Processing;
+using SixLabors.ImageSharp.Processing;
 using System.IO;
+using System.Text;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
 
 namespace Masuit.Tools.Media
 {
@@ -19,12 +23,19 @@ namespace Masuit.Tools.Media
 
         private readonly Stream _stream;
 
-        [Obsolete(nameof(ImageWatermarker) + "是基于System.Drawing实现的,System.Drawing将在.NET7开始只支持Windows下运行,如果您的项目需要在Linux下部署,请考虑使用ImageSharp或SkiaSharp之类的替代类库重新实现")]
         public ImageWatermarker(Stream originStream)
         {
             _stream = originStream;
         }
 
+        public MemoryStream AddWatermark(string watermarkText, string ttfFontPath, int fontSize, Color color, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int textPadding = 10)
+        {
+            var fonts = new FontCollection();
+            var fontFamily = fonts.Add(ttfFontPath); //字体的路径(电脑自带字体库,去copy出来)
+            var font = new Font(fontFamily, fontSize, FontStyle.Bold);
+            return AddWatermark(watermarkText, font, color, watermarkPosition, textPadding);
+        }
+
         /// <summary>
         /// 添加水印
         /// </summary>
@@ -32,57 +43,39 @@ namespace Masuit.Tools.Media
         /// <param name="color">水印颜色</param>
         /// <param name="watermarkPosition">水印位置</param>
         /// <param name="textPadding">边距</param>
-        /// <param name="fontSize">字体大小</param>
         /// <param name="font">字体</param>
-        /// <param name="textAntiAlias">不提示的情况下使用抗锯齿标志符号位图来绘制每个字符。
-        ///    由于抗锯齿质量就越好。
-        ///    因为关闭了提示,词干宽度之间的差异可能非常明显。</param>
         /// <returns></returns>
-        [Obsolete(nameof(ImageWatermarker) + "." + nameof(AddWatermark) + "是基于System.Drawing实现的,System.Drawing将在.NET7开始只支持Windows下运行,如果您的项目需要在Linux下部署,请考虑使用ImageSharp或SkiaSharp之类的替代类库重新实现")]
-        public MemoryStream AddWatermark(string watermarkText, Color color, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int textPadding = 10, int fontSize = 20, Font font = null, bool textAntiAlias = true)
+        public MemoryStream AddWatermark(string watermarkText, Font font, Color color, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int textPadding = 10)
         {
-            using var img = Image.FromStream(_stream);
+            using var img = Image.Load(_stream);
             if (SkipWatermarkForSmallImages && (img.Height < Math.Sqrt(SmallImagePixelsThreshold) || img.Width < Math.Sqrt(SmallImagePixelsThreshold)))
             {
-                return _stream.SaveAsMemoryStream();
-            }
-
-            using var graphic = Graphics.FromImage(img);
-            if (textAntiAlias)
-            {
-                graphic.TextRenderingHint = TextRenderingHint.AntiAlias;
+                return _stream as MemoryStream ?? _stream.SaveAsMemoryStream();
             }
 
-            using var brush = new SolidBrush(color);
-            if (img.Width / fontSize > 50)
+            if (img.Width / font.Size > 50)
             {
-                fontSize = img.Width / 50;
+                font = font.Family.CreateFont(img.Width * 1f / 50);
             }
 
-            using var f = font ?? new Font(FontFamily.GenericSansSerif, fontSize, FontStyle.Bold, GraphicsUnit.Pixel);
-            var textSize = graphic.MeasureString(watermarkText, f);
-            int x, y;
+            var measure = TextMeasurer.Measure(watermarkText, new TextOptions(font));
+            float x, y;
             textPadding += (img.Width - 1000) / 100;
             switch (watermarkPosition)
             {
-                case WatermarkPosition.TopLeft:
-                    x = textPadding;
-                    y = textPadding;
-                    break;
-
                 case WatermarkPosition.TopRight:
-                    x = img.Width - (int)textSize.Width - textPadding;
+                    x = img.Width - measure.Width - textPadding;
                     y = textPadding;
                     break;
 
                 case WatermarkPosition.BottomLeft:
                     x = textPadding;
-                    y = img.Height - (int)textSize.Height - textPadding;
+                    y = img.Height - measure.Height - textPadding;
                     break;
 
                 case WatermarkPosition.BottomRight:
-                    x = img.Width - (int)textSize.Width - textPadding;
-                    y = img.Height - (int)textSize.Height - textPadding;
+                    x = img.Width - measure.Width - textPadding;
+                    y = img.Height - measure.Height - textPadding;
                     break;
 
                 default:
@@ -91,9 +84,74 @@ namespace Masuit.Tools.Media
                     break;
             }
 
-            graphic.DrawString(watermarkText, f, brush, new Point(x, y));
+            img.Mutate(c => c.DrawText(watermarkText, font, color, new PointF(x, y)));
+            var ms = new MemoryStream();
+            img.SaveAsWebp(ms);
+            ms.Position = 0;
+            return ms;
+        }
+
+        /// <summary>
+        /// 添加水印
+        /// </summary>
+        /// <param name="watermarkImage">水印图片</param>
+        /// <param name="opacity">水印图片</param>
+        /// <param name="watermarkPosition">水印位置</param>
+        /// <param name="padding">水印边距</param>
+        /// <returns></returns>
+        public MemoryStream AddWatermark(Stream watermarkImage, float opacity = 1f, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int padding = 20)
+        {
+            using var img = Image.Load(_stream);
+            var height = img.Height;
+            var width = img.Width;
+            if (SkipWatermarkForSmallImages && (height < Math.Sqrt(SmallImagePixelsThreshold) || width < Math.Sqrt(SmallImagePixelsThreshold)))
+            {
+                return _stream as MemoryStream ?? _stream.SaveAsMemoryStream();
+            }
+
+            var watermark = Image.Load(watermarkImage);
+            watermark.Mutate(c => c.Resize(new ResizeOptions()
+            {
+                Size = new Size
+                {
+                    Width = width / 10,
+                    Height = height / 10,
+                },
+                Mode = ResizeMode.Pad,
+                Sampler = new BicubicResampler()
+            }));
+            int x, y;
+            padding += (width - 1000) / 100;
+            switch (watermarkPosition)
+            {
+                case WatermarkPosition.TopRight:
+                    x = width - watermark.Width - padding;
+                    y = padding;
+                    break;
+
+                case WatermarkPosition.BottomLeft:
+                    x = padding;
+                    y = height - watermark.Height - padding;
+                    break;
+
+                case WatermarkPosition.BottomRight:
+                    x = width - watermark.Width - padding;
+                    y = height - watermark.Height - padding;
+                    break;
+
+                default:
+                    x = padding;
+                    y = padding;
+                    break;
+            }
+
+            img.Mutate(c =>
+            {
+                c.DrawImage(watermark, new Point(x, y), opacity);
+                watermark.Dispose();
+            });
             var ms = new MemoryStream();
-            img.Save(ms, img.RawFormat);
+            img.SaveAsWebp(ms);
             ms.Position = 0;
             return ms;
         }

+ 0 - 14
Masuit.Tools.Abstractions/Reflection/ReflectionUtil.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
-using System.Drawing;
 using System.IO;
 using System.Linq;
 using System.Linq.Expressions;
@@ -231,19 +230,6 @@ namespace Masuit.Tools.Reflection
             return asm.GetManifestResourceStream(resourceName);
         }
 
-        /// <summary>
-        /// 获取程序集资源的位图资源
-        /// </summary>
-        /// <param name="assemblyType">程序集中的某一对象类型</param>
-        /// <param name="resourceHolder">资源的根名称。例如,名为“MyResource.en-US.resources”的资源文件的根名称为“MyResource”。</param>
-        /// <param name="imageName">资源项名称</param>
-        public static Bitmap LoadBitmap(this Type assemblyType, string resourceHolder, string imageName)
-        {
-            Assembly thisAssembly = Assembly.GetAssembly(assemblyType);
-            ResourceManager rm = new ResourceManager(resourceHolder, thisAssembly);
-            return (Bitmap)rm.GetObject(imageName);
-        }
-
         /// <summary>
         ///  获取程序集资源的文本资源
         /// </summary>

+ 122 - 0
Masuit.Tools.Abstractions/Strings/SimHash.cs

@@ -0,0 +1,122 @@
+using System.Linq;
+using System.Numerics;
+
+namespace Masuit.Tools.Strings;
+
+public class SimHash
+{
+    private readonly string _tokens;
+    private readonly BigInteger _strSimHash;
+    private readonly int _hashBits = 128;
+
+    public BigInteger StrSimHash => _strSimHash;
+
+    public SimHash(string tokens, int hashBits)
+    {
+        _tokens = tokens;
+        _hashBits = hashBits;
+        _strSimHash = GetSimHash();
+    }
+
+    public SimHash(string tokens)
+    {
+        _tokens = tokens;
+        _strSimHash = GetSimHash();
+    }
+
+    private BigInteger GetSimHash()
+    {
+        var v = new int[_hashBits];
+        var stringTokens = new SimTokenizer(_tokens);
+        while (stringTokens.HasMoreTokens())
+        {
+            var temp = stringTokens.NextToken();
+            var t = Hash(temp);
+            for (var i = 0; i < _hashBits; i++)
+            {
+                var bitmask = BigInteger.One << i;
+                if ((t & bitmask).Sign != 0)
+                {
+                    v[i] += 1;
+                }
+                else
+                {
+                    v[i] -= 1;
+                }
+            }
+        }
+
+        var fingerprint = BigInteger.Zero;
+        for (var i = 0; i < _hashBits; i++)
+        {
+            if (v[i] >= 0)
+            {
+                fingerprint += BigInteger.Parse("1") << i;
+            }
+        }
+
+        return fingerprint;
+    }
+
+    private BigInteger Hash(string source)
+    {
+        if (string.IsNullOrEmpty(source))
+        {
+            return BigInteger.Zero;
+        }
+
+        var sourceArray = source.ToCharArray();
+        var x = new BigInteger((long)sourceArray[0] << 7);
+        var m = BigInteger.Parse("1000003");
+        var mask = BigInteger.Pow(new BigInteger(2), _hashBits) - BigInteger.One;
+        x = sourceArray.Select(item => new BigInteger((long)item)).Aggregate(x, (current, temp) => ((current * m) ^ temp) & mask);
+        x ^= new BigInteger(source.Length);
+        if (x.Equals(BigInteger.MinusOne))
+        {
+            x = new BigInteger(-2);
+        }
+
+        return x;
+    }
+
+    public int HammingDistance(SimHash other)
+    {
+        var m = (BigInteger.One << _hashBits) - BigInteger.One;
+        var x = (_strSimHash ^ other._strSimHash) & m;
+        var tot = 0;
+        while (x.Sign != 0)
+        {
+            tot += 1;
+            x &= x - BigInteger.One;
+        }
+
+        return tot;
+    }
+}
+
+//简单的分词法,直接将中文分成单个汉字。可以用其他分词法代替
+public class SimTokenizer
+{
+    private readonly string _source;
+    private int _index;
+    private readonly int _length;
+
+    public SimTokenizer(string source)
+    {
+        _source = source;
+        _index = 0;
+        _length = (source ?? "").Length;
+    }
+
+    public bool HasMoreTokens()
+    {
+        return _index < _length;
+    }
+
+    public string NextToken()
+    {
+        var s = _source.Substring(_index, 1);
+        _index++;
+        return s;
+    }
+}

+ 41 - 33
Masuit.Tools.Abstractions/Strings/ValidateCode.cs

@@ -1,17 +1,23 @@
 #if NET461
 using System.Web;
 #else
+
 using Microsoft.AspNetCore.Http;
+
 #endif
 
 using System;
-using System.Drawing;
-using System.Drawing.Drawing2D;
 using System.IO;
+using System.Linq;
 using System.Security.Cryptography;
 using System.Text;
-using System.Drawing.Imaging;
 using Masuit.Tools.AspNetCore.Mime;
+using SixLabors.Fonts;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Drawing.Processing;
+using SixLabors.ImageSharp.Formats.Webp;
 
 namespace Masuit.Tools.Strings
 {
@@ -29,7 +35,7 @@ namespace Masuit.Tools.Strings
         {
             string ch = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ1234567890@#$%&?";
             byte[] b = new byte[4];
-            using var cpt = new RNGCryptoServiceProvider();
+            using var cpt = RandomNumberGenerator.Create();
             cpt.GetBytes(b);
             var r = new Random(BitConverter.ToInt32(b, 0));
             var sb = new StringBuilder();
@@ -46,52 +52,54 @@ namespace Masuit.Tools.Strings
         /// </summary>
         /// <param name="validateCode">验证码序列</param>
         /// <param name="context">当前的HttpContext上下文对象</param>
-        /// <param name="fontSize">字体大小,默认值22px</param>
-        /// <param name="lineHeight">行高,默认36px</param>
+        /// <param name="fontSize">字体大小,默认值28px</param>
         /// <exception cref="Exception">The operation failed.</exception>
-        public static byte[] CreateValidateGraphic(this HttpContext context, string validateCode, int fontSize = 22, int lineHeight = 36)
+        public static byte[] CreateValidateGraphic(this HttpContext context, string validateCode, int fontSize = 28)
         {
-            using Bitmap image = new Bitmap((int)Math.Ceiling(validateCode.Length * (fontSize + 2.0)), lineHeight + 2);
-            using Graphics g = Graphics.FromImage(image);
+            var font = SystemFonts.Families.Where(f => new[] { "Consolas", "KaiTi", "NSimSun", "SimSun", "SimHei", "Microsoft YaHei UI", "Arial" }.Contains(f.Name)).OrderByRandom().FirstOrDefault().CreateFont(fontSize);
+            var measure = TextMeasurer.Measure(validateCode, new TextOptions(font));
+            var width = (int)Math.Ceiling(measure.Width * 1.5);
+            var height = (int)Math.Ceiling(measure.Height + 5);
+            using var image = new Image<Rgba32>(width, height);
+
             //生成随机生成器
             Random random = new Random();
+
             //清空图片背景色
-            g.Clear(Color.White);
-            //画图片的干扰线
-            for (int i = 0; i < 75; i++)
+            image.Mutate(g =>
             {
-                int x1 = random.StrictNext(image.Width);
-                int x2 = random.StrictNext(image.Width);
-                int y1 = random.StrictNext(image.Height);
-                int y2 = random.StrictNext(image.Height);
-                g.DrawLine(new Pen(Color.FromArgb(random.StrictNext(255), random.StrictNext(255), random.StrictNext(255))), x1, y1, x2, y2);
-            }
+                g.BackgroundColor(Color.White);
 
-            Font[] fonts =
-            {
-                new Font("Arial", fontSize, FontStyle.Bold | FontStyle.Italic),
-                new Font("微软雅黑", fontSize, FontStyle.Bold | FontStyle.Italic),
-                new Font("黑体", fontSize, FontStyle.Bold | FontStyle.Italic),
-                new Font("宋体", fontSize, FontStyle.Bold | FontStyle.Italic),
-                new Font("楷体", fontSize, FontStyle.Bold | FontStyle.Italic)
-            };
-            //渐变.
-            using var brush = new LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.DarkRed, 1.2f, true);
-            g.DrawString(validateCode, fonts[random.StrictNext(fonts.Length)], brush, 3, 2);
+                //画图片的干扰线
+                for (int i = 0; i < 75; i++)
+                {
+                    int x1 = random.StrictNext(width);
+                    int x2 = random.StrictNext(width);
+                    int y1 = random.StrictNext(height);
+                    int y2 = random.StrictNext(height);
+                    g.DrawLines(new Pen(new Color(new Rgba32((byte)random.StrictNext(255), (byte)random.StrictNext(255), (byte)random.StrictNext(255))), 1), new PointF(x1, y1), new PointF(x2, y2));
+                }
+
+                //渐变.
+                var brush = new LinearGradientBrush(new PointF(0, 0), new PointF(width, height), GradientRepetitionMode.Repeat, new ColorStop(0.5f, Color.Blue), new ColorStop(0.5f, Color.DarkRed));
+                g.DrawText(validateCode, font, brush, new PointF(3, 2));
+
+                //画图片的边框线
+                g.DrawLines(new Pen(Color.Silver, 1), new PointF(0, 0), new PointF(width - 1, height - 1));
+            });
 
             //画图片的前景干扰点
             for (int i = 0; i < 350; i++)
             {
                 int x = random.StrictNext(image.Width);
                 int y = random.StrictNext(image.Height);
-                image.SetPixel(x, y, Color.FromArgb(random.StrictNext(255), random.StrictNext(255), random.StrictNext(255)));
+                image[x, y] = new Rgba32(random.StrictNext(255), random.StrictNext(255), random.StrictNext(255));
             }
 
-            //画图片的边框线
-            g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1);
             //保存图片数据
             using MemoryStream stream = new MemoryStream();
-            image.Save(stream, ImageFormat.Jpeg);
+            image.Save(stream, WebpFormat.Instance);
+
             //输出图片流
             context.Response.Clear();
             context.Response.ContentType = ContentType.Jpeg;

+ 1 - 1
Masuit.Tools.AspNetCore/Masuit.Tools.AspNetCore.csproj

@@ -18,7 +18,7 @@
         <LangVersion>latest</LangVersion>
         <RepositoryType>Github</RepositoryType>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
-        <Version>1.0.6.4</Version>
+        <Version>1.1</Version>
         <FileVersion>1.0</FileVersion>
         <Company>masuit.com</Company>
         <AssemblyVersion>1.0</AssemblyVersion>

+ 3 - 5
Masuit.Tools.Core/Masuit.Tools.Core.csproj

@@ -18,7 +18,7 @@ github:https://github.com/ldqk/Masuit.Tools
         <UserSecretsId>830c282f-f7c1-42be-8651-4cd06ac8e73f</UserSecretsId>
         <RepositoryType>Github</RepositoryType>
         <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
-        <Version>2.4.9.4</Version>
+        <Version>2.5</Version>
         <FileVersion>2.4.5.6</FileVersion>
         <Company>masuit.com</Company>
         <AssemblyVersion>2.4.5.6</AssemblyVersion>
@@ -34,6 +34,8 @@ github:https://github.com/ldqk/Masuit.Tools
         <PackageReference Include="DnsClient" Version="1.6.1" />
         <PackageReference Include="HtmlSanitizer" Version="7.1.512" />
         <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
+        <PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
+        <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14" />
         <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
         <PackageReference Include="SharpCompress" Version="0.32.1" />
     </ItemGroup>
@@ -44,7 +46,6 @@ github:https://github.com/ldqk/Masuit.Tools
         <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
         <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="4.7.0" />
-        <PackageReference Include="System.Drawing.Common" Version="4.5.0" />
         <PackageReference Include="System.Management" Version="4.7.0" />
     </ItemGroup>
     <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1'">
@@ -53,7 +54,6 @@ github:https://github.com/ldqk/Masuit.Tools
         <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.9" />
         <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.9" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="4.7.0" />
-        <PackageReference Include="System.Drawing.Common" Version="5.0.0" />
         <PackageReference Include="System.Management" Version="4.7.0" />
     </ItemGroup>
     <ItemGroup Condition=" '$(TargetFramework)' == 'net5'">
@@ -62,7 +62,6 @@ github:https://github.com/ldqk/Masuit.Tools
         <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0" />
         <PackageReference Include="Microsoft.Extensions.Http" Version="5.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="5.0.1" />
-        <PackageReference Include="System.Drawing.Common" Version="5.0.2" />
         <PackageReference Include="System.Management" Version="5.0" />
     </ItemGroup>
     <ItemGroup Condition=" '$(TargetFramework)' == 'net6'">
@@ -71,7 +70,6 @@ github:https://github.com/ldqk/Masuit.Tools
         <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0" />
         <PackageReference Include="Microsoft.Extensions.Http" Version="6.0" />
         <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="6.0.1" />
-        <PackageReference Include="System.Drawing.Common" Version="6.0" />
         <PackageReference Include="System.Management" Version="6.0" />
     </ItemGroup>
     <ItemGroup>

+ 5 - 6
Masuit.Tools.Net45/Masuit.Tools.Net45.csproj

@@ -139,12 +139,6 @@
     <Compile Include="..\Masuit.Tools.Abstractions\Maths\Vector2D.cs">
       <Link>Maths\Vector2D.cs</Link>
     </Compile>
-    <Compile Include="..\Masuit.Tools.Abstractions\Media\ImageUtilities.cs">
-      <Link>Media\ImageUtilities.cs</Link>
-    </Compile>
-    <Compile Include="..\Masuit.Tools.Abstractions\Media\ImageWatermarker.cs">
-      <Link>Media\ImageWatermarker.cs</Link>
-    </Compile>
     <Compile Include="..\Masuit.Tools.Abstractions\Media\ThumbnailCutMode.cs">
       <Link>Media\ThumbnailCutMode.cs</Link>
     </Compile>
@@ -256,6 +250,9 @@
     <Compile Include="..\Masuit.Tools.Abstractions\Strings\NumberFormater.cs">
       <Link>Strings\NumberFormater.cs</Link>
     </Compile>
+    <Compile Include="..\Masuit.Tools.Abstractions\Strings\SimHash.cs">
+      <Link>Strings\SimHash.cs</Link>
+    </Compile>
     <Compile Include="..\Masuit.Tools.Abstractions\Strings\Template.cs">
       <Link>Strings\Template.cs</Link>
     </Compile>
@@ -337,6 +334,8 @@
     <Compile Include="..\Masuit.Tools\Strings\ValidateCode.cs">
       <Link>Strings\ValidateCode.cs</Link>
     </Compile>
+    <Compile Include="Media\ImageUtilities.cs" />
+    <Compile Include="Media\ImageWatermarker.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>

+ 1101 - 0
Masuit.Tools.Net45/Media/ImageUtilities.cs

@@ -0,0 +1,1101 @@
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Masuit.Tools.Media
+{
+    /// <summary>
+    /// 图片处理
+    /// </summary>
+    public static class ImageUtilities
+    {
+        #region 正方型裁剪并缩放
+
+        /// <summary>
+        /// 正方型裁剪
+        /// 以图片中心为轴心,截取正方型,然后等比缩放
+        /// 用于头像处理
+        /// </summary>
+        /// <param name="fromFile">原图Stream对象</param>
+        /// <param name="fileSaveUrl">缩略图存放地址</param>
+        /// <param name="side">指定的边长(正方型)</param>
+        /// <param name="quality">质量(范围0-100)</param>
+        public static void CutForSquare(this Stream fromFile, string fileSaveUrl, int side, int quality)
+        {
+            //创建目录
+            string dir = Path.GetDirectoryName(fileSaveUrl);
+            Directory.CreateDirectory(dir);
+
+            //原始图片(获取原始图片创建对象,并使用流中嵌入的颜色管理信息)
+            var initImage = Image.FromStream(fromFile, true);
+
+            //原图宽高均小于模版,不作处理,直接保存
+            if ((initImage.Width <= side) && (initImage.Height <= side))
+            {
+                initImage.Save(fileSaveUrl);
+            }
+            else
+            {
+                //原始图片的宽、高
+                int initWidth = initImage.Width;
+                int initHeight = initImage.Height;
+
+                //非正方型先裁剪为正方型
+                if (initWidth != initHeight)
+                {
+                    //截图对象
+                    Image pickedImage;
+                    Graphics pickedG;
+
+                    //宽大于高的横图
+                    if (initWidth > initHeight)
+                    {
+                        //对象实例化
+                        pickedImage = new Bitmap(initHeight, initHeight);
+                        pickedG = Graphics.FromImage(pickedImage);
+
+                        //设置质量
+                        pickedG.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                        pickedG.SmoothingMode = SmoothingMode.HighQuality;
+
+                        //定位
+                        Rectangle fromR = new Rectangle((initWidth - initHeight) / 2, 0, initHeight, initHeight);
+                        Rectangle toR = new Rectangle(0, 0, initHeight, initHeight);
+
+                        //画图
+                        pickedG.DrawImage(initImage, toR, fromR, GraphicsUnit.Pixel);
+
+                        //重置宽
+                        initWidth = initHeight;
+                    }
+
+                    //高大于宽的竖图
+                    else
+                    {
+                        //对象实例化
+                        pickedImage = new Bitmap(initWidth, initWidth);
+                        pickedG = Graphics.FromImage(pickedImage);
+
+                        //设置质量
+                        pickedG.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                        pickedG.SmoothingMode = SmoothingMode.HighQuality;
+
+                        //定位
+                        Rectangle fromR = new Rectangle(0, (initHeight - initWidth) / 2, initWidth, initWidth);
+                        Rectangle toR = new Rectangle(0, 0, initWidth, initWidth);
+
+                        //画图
+                        pickedG.DrawImage(initImage, toR, fromR, GraphicsUnit.Pixel);
+
+                        //重置高
+                        initHeight = initWidth;
+                    }
+
+                    //将截图对象赋给原图
+                    initImage = (Image)pickedImage.Clone();
+
+                    //释放截图资源
+                    initImage.Dispose();
+                    pickedG.Dispose();
+                    pickedImage.Dispose();
+                }
+
+                //缩略图对象
+                using Image resultImage = new Bitmap(side, side);
+                using var resultG = Graphics.FromImage(resultImage);
+
+                //设置质量
+                resultG.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                resultG.SmoothingMode = SmoothingMode.HighQuality;
+
+                //用指定背景色清空画布
+                resultG.Clear(Color.White);
+
+                //绘制缩略图
+                resultG.DrawImage(initImage, new Rectangle(0, 0, side, side), new Rectangle(0, 0, initWidth, initHeight), GraphicsUnit.Pixel);
+
+                //关键质量控制
+                //获取系统编码类型数组,包含了jpeg,bmpp,png,gif,tiff
+                var icis = ImageCodecInfo.GetImageEncoders();
+                ImageCodecInfo ici = null;
+                foreach (var i in icis)
+                {
+                    if ((i.MimeType == "image/jpeg") || (i.MimeType == "image/bmpp") || (i.MimeType == "image/png") || (i.MimeType == "image/gif"))
+                        ici = i;
+                }
+
+                using var ep = new EncoderParameters(1)
+                {
+                    Param =
+                    {
+                        [0] = new EncoderParameter(Encoder.Quality, quality)
+                    }
+                };
+
+                //保存缩略图
+                resultImage.Save(fileSaveUrl, ici, ep);
+            }
+        }
+
+        #endregion 正方型裁剪并缩放
+
+        #region 自定义裁剪并缩放
+
+        /// <summary>
+        /// 指定长宽裁剪
+        /// 按模版比例最大范围的裁剪图片并缩放至模版尺寸
+        /// </summary>
+        /// <param name="fromFile">原图Stream对象</param>
+        /// <param name="fileSaveUrl">保存路径</param>
+        /// <param name="maxWidth">最大宽(单位:px)</param>
+        /// <param name="maxHeight">最大高(单位:px)</param>
+        /// <param name="quality">质量(范围0-100)</param>
+        public static void CutForCustom(this Stream fromFile, string fileSaveUrl, int maxWidth, int maxHeight, int quality)
+        {
+            //从文件获取原始图片,并使用流中嵌入的颜色管理信息
+            using var initImage = Image.FromStream(fromFile, true);
+
+            //原图宽高均小于模版,不作处理,直接保存
+            if ((initImage.Width <= maxWidth) && (initImage.Height <= maxHeight))
+            {
+                initImage.Save(fileSaveUrl);
+            }
+            else
+            {
+                //模版的宽高比例
+                double templateRate = (double)maxWidth / maxHeight;
+
+                //原图片的宽高比例
+                double initRate = (double)initImage.Width / initImage.Height;
+
+                //原图与模版比例相等,直接缩放
+                if (templateRate == initRate)
+                {
+                    //按模版大小生成最终图片
+                    Image templateImage = new Bitmap(maxWidth, maxHeight);
+                    Graphics templateG = Graphics.FromImage(templateImage);
+                    templateG.InterpolationMode = InterpolationMode.High;
+                    templateG.SmoothingMode = SmoothingMode.HighQuality;
+                    templateG.Clear(Color.White);
+                    templateG.DrawImage(initImage, new Rectangle(0, 0, maxWidth, maxHeight), new Rectangle(0, 0, initImage.Width, initImage.Height), GraphicsUnit.Pixel);
+                    templateImage.Save(fileSaveUrl, initImage.RawFormat);
+                }
+
+                //原图与模版比例不等,裁剪后缩放
+                else
+                {
+                    //裁剪对象
+                    Image pickedImage;
+                    Graphics pickedG;
+
+                    //定位
+                    Rectangle fromR = new Rectangle(0, 0, 0, 0); //原图裁剪定位
+                    Rectangle toR = new Rectangle(0, 0, 0, 0); //目标定位
+
+                    //宽为标准进行裁剪
+                    if (templateRate > initRate)
+                    {
+                        //裁剪对象实例化
+                        pickedImage = new Bitmap(initImage.Width, (int)Math.Floor(initImage.Width / templateRate));
+                        pickedG = Graphics.FromImage(pickedImage);
+
+                        //裁剪源定位
+                        fromR.X = 0;
+                        fromR.Y = (int)Math.Floor((initImage.Height - initImage.Width / templateRate) / 2);
+                        fromR.Width = initImage.Width;
+                        fromR.Height = (int)Math.Floor(initImage.Width / templateRate);
+
+                        //裁剪目标定位
+                        toR.X = 0;
+                        toR.Y = 0;
+                        toR.Width = initImage.Width;
+                        toR.Height = (int)Math.Floor(initImage.Width / templateRate);
+                    }
+
+                    //高为标准进行裁剪
+                    else
+                    {
+                        pickedImage = new Bitmap((int)Math.Floor(initImage.Height * templateRate), initImage.Height);
+                        pickedG = Graphics.FromImage(pickedImage);
+
+                        fromR.X = (int)Math.Floor((initImage.Width - initImage.Height * templateRate) / 2);
+                        fromR.Y = 0;
+                        fromR.Width = (int)Math.Floor(initImage.Height * templateRate);
+                        fromR.Height = initImage.Height;
+
+                        toR.X = 0;
+                        toR.Y = 0;
+                        toR.Width = (int)Math.Floor(initImage.Height * templateRate);
+                        toR.Height = initImage.Height;
+                    }
+
+                    //设置质量
+                    pickedG.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                    pickedG.SmoothingMode = SmoothingMode.HighQuality;
+
+                    //裁剪
+                    pickedG.DrawImage(initImage, toR, fromR, GraphicsUnit.Pixel);
+
+                    //按模版大小生成最终图片
+                    using Image templateImage = new Bitmap(maxWidth, maxHeight);
+                    using Graphics templateG = Graphics.FromImage(templateImage);
+                    templateG.InterpolationMode = InterpolationMode.High;
+                    templateG.SmoothingMode = SmoothingMode.HighQuality;
+                    templateG.Clear(Color.White);
+                    templateG.DrawImage(pickedImage, new Rectangle(0, 0, maxWidth, maxHeight), new Rectangle(0, 0, pickedImage.Width, pickedImage.Height), GraphicsUnit.Pixel);
+
+                    //关键质量控制
+                    //获取系统编码类型数组,包含了jpeg,bmpp,png,gif,tiff
+                    ImageCodecInfo[] icis = ImageCodecInfo.GetImageEncoders();
+                    ImageCodecInfo ici = null;
+                    foreach (var i in icis)
+                    {
+                        if (i.MimeType == "image/jpeg" || i.MimeType == "image/bmpp" || i.MimeType == "image/png" || i.MimeType == "image/gif")
+                            ici = i;
+                    }
+
+                    EncoderParameters ep = new EncoderParameters(1);
+                    ep.Param[0] = new EncoderParameter(Encoder.Quality, quality);
+
+                    //保存缩略图
+                    templateImage.Save(fileSaveUrl, ici, ep);
+                    pickedG.Dispose();
+                    pickedImage.Dispose();
+                }
+            }
+        }
+
+        #endregion 自定义裁剪并缩放
+
+        #region 等比缩放
+
+        /// <summary>
+        /// 图片等比缩放
+        /// </summary>
+        /// <param name="fromFile">原图Stream对象</param>
+        /// <param name="savePath">缩略图存放地址</param>
+        /// <param name="targetWidth">指定的最大宽度</param>
+        /// <param name="targetHeight">指定的最大高度</param>
+        /// <param name="watermarkText">水印文字(为""表示不使用水印)</param>
+        /// <param name="watermarkImage">水印图片路径(为""表示不使用水印)</param>
+        public static void ZoomAuto(this Stream fromFile, string savePath, double targetWidth, double targetHeight, string watermarkText, string watermarkImage)
+        {
+            //创建目录
+            string dir = Path.GetDirectoryName(savePath);
+            Directory.CreateDirectory(dir);
+
+            //原始图片(获取原始图片创建对象,并使用流中嵌入的颜色管理信息)
+            using Image initImage = Image.FromStream(fromFile, true);
+
+            //原图宽高均小于模版,不作处理,直接保存
+            if ((initImage.Width <= targetWidth) && (initImage.Height <= targetHeight))
+            {
+                //文字水印
+                if (!string.IsNullOrEmpty(watermarkText))
+                {
+                    using var gWater = Graphics.FromImage(initImage);
+                    Font fontWater = new Font("黑体", 10);
+                    Brush brushWater = new SolidBrush(Color.White);
+                    gWater.DrawString(watermarkText, fontWater, brushWater, 10, 10);
+                    gWater.Dispose();
+                }
+
+                //透明图片水印
+                if (!string.IsNullOrEmpty(watermarkImage))
+                {
+                    if (File.Exists(watermarkImage))
+                    {
+                        using var wrImage = Image.FromFile(watermarkImage);
+
+                        //水印绘制条件:原始图片宽高均大于或等于水印图片
+                        if ((initImage.Width >= wrImage.Width) && (initImage.Height >= wrImage.Height))
+                        {
+                            Graphics gWater = Graphics.FromImage(initImage);
+
+                            //透明属性
+                            ImageAttributes imgAttributes = new ImageAttributes();
+                            ColorMap colorMap = new ColorMap();
+                            colorMap.OldColor = Color.FromArgb(255, 0, 255, 0);
+                            colorMap.NewColor = Color.FromArgb(0, 0, 0, 0);
+                            ColorMap[] remapTable = { colorMap };
+                            imgAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);
+
+                            float[][] colorMatrixElements =
+                            {
+                                new[] {1.0f, 0.0f, 0.0f, 0.0f, 0.0f},
+                                new[] {0.0f, 1.0f, 0.0f, 0.0f, 0.0f},
+                                new[] {0.0f, 0.0f, 1.0f, 0.0f, 0.0f},
+                                new[] {0.0f, 0.0f, 0.0f, 0.5f, 0.0f}, //透明度:0.5
+                                new[] {0.0f, 0.0f, 0.0f, 0.0f, 1.0f}
+                            };
+
+                            ColorMatrix wmColorMatrix = new ColorMatrix(colorMatrixElements);
+                            imgAttributes.SetColorMatrix(wmColorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
+                            gWater.DrawImage(wrImage, new Rectangle(initImage.Width - wrImage.Width, initImage.Height - wrImage.Height, wrImage.Width, wrImage.Height), 0, 0, wrImage.Width, wrImage.Height, GraphicsUnit.Pixel, imgAttributes);
+
+                            gWater.Dispose();
+                        }
+                        wrImage.Dispose();
+                    }
+                }
+
+                //保存
+                initImage.Save(savePath, initImage.RawFormat);
+            }
+            else
+            {
+                //缩略图宽、高计算
+                double newWidth = initImage.Width;
+                double newHeight = initImage.Height;
+
+                //宽大于高或宽等于高(横图或正方)
+                if ((initImage.Width > initImage.Height) || (initImage.Width == initImage.Height))
+                {
+                    //如果宽大于模版
+                    if (initImage.Width > targetWidth)
+                    {
+                        //宽按模版,高按比例缩放
+                        newWidth = targetWidth;
+                        newHeight = initImage.Height * (targetWidth / initImage.Width);
+                    }
+                }
+
+                //高大于宽(竖图)
+                else
+                {
+                    //如果高大于模版
+                    if (initImage.Height > targetHeight)
+                    {
+                        //高按模版,宽按比例缩放
+                        newHeight = targetHeight;
+                        newWidth = initImage.Width * (targetHeight / initImage.Height);
+                    }
+                }
+
+                //生成新图
+                //新建一个bmpp图片
+                using Image newImage = new Bitmap((int)newWidth, (int)newHeight);
+
+                //新建一个画板
+                using Graphics newG = Graphics.FromImage(newImage);
+
+                //设置质量
+                newG.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                newG.SmoothingMode = SmoothingMode.HighQuality;
+
+                //置背景色
+                newG.Clear(Color.White);
+
+                //画图
+                newG.DrawImage(initImage, new Rectangle(0, 0, newImage.Width, newImage.Height), new Rectangle(0, 0, initImage.Width, initImage.Height), GraphicsUnit.Pixel);
+
+                //文字水印
+                if (!string.IsNullOrEmpty(watermarkText))
+                {
+                    using var gWater = Graphics.FromImage(newImage);
+                    Font fontWater = new Font("微软雅黑", 10);
+                    Brush brushWater = new SolidBrush(Color.White);
+                    gWater.DrawString(watermarkText, fontWater, brushWater, 10, 10);
+                    gWater.Dispose();
+                }
+
+                //透明图片水印
+                if (!string.IsNullOrEmpty(watermarkImage))
+                {
+                    if (File.Exists(watermarkImage))
+                    {
+                        using Image wrImage = Image.FromFile(watermarkImage);
+
+                        //水印绘制条件:原始图片宽高均大于或等于水印图片
+                        if ((newImage.Width >= wrImage.Width) && (newImage.Height >= wrImage.Height))
+                        {
+                            Graphics gWater = Graphics.FromImage(newImage);
+
+                            //透明属性
+                            ImageAttributes imgAttributes = new ImageAttributes();
+                            ColorMap colorMap = new ColorMap
+                            {
+                                OldColor = Color.FromArgb(255, 0, 255, 0),
+                                NewColor = Color.FromArgb(0, 0, 0, 0)
+                            };
+                            ColorMap[] remapTable = { colorMap };
+                            imgAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);
+
+                            float[][] colorMatrixElements =
+                            {
+                                new[] {1.0f, 0.0f, 0.0f, 0.0f, 0.0f},
+                                new[] {0.0f, 1.0f, 0.0f, 0.0f, 0.0f},
+                                new[] {0.0f, 0.0f, 1.0f, 0.0f, 0.0f},
+                                new[] {0.0f, 0.0f, 0.0f, 0.5f, 0.0f}, //透明度:0.5
+                                new[] {0.0f, 0.0f, 0.0f, 0.0f, 1.0f}
+                            };
+
+                            ColorMatrix wmColorMatrix = new ColorMatrix(colorMatrixElements);
+                            imgAttributes.SetColorMatrix(wmColorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
+                            gWater.DrawImage(wrImage, new Rectangle(newImage.Width - wrImage.Width, newImage.Height - wrImage.Height, wrImage.Width, wrImage.Height), 0, 0, wrImage.Width, wrImage.Height, GraphicsUnit.Pixel, imgAttributes);
+                            gWater.Dispose();
+                        }
+                    }
+                }
+
+                //保存缩略图
+                newImage.Save(savePath, initImage.RawFormat);
+            }
+        }
+
+        #endregion 等比缩放
+
+        #region 判断文件类型是否为WEB格式图片
+
+        /// <summary>
+        /// 判断文件类型是否为WEB格式图片
+        /// (注:JPG,GIF,BMP,PNG)
+        /// </summary>
+        /// <param name="contentType">HttpPostedFile.ContentType</param>
+        /// <returns>是否为WEB格式图片</returns>
+        public static bool IsWebImage(string contentType)
+        {
+            return contentType == "image/pjpeg" || contentType == "image/jpeg" || contentType == "image/gif" || contentType == "image/bmpp" || contentType == "image/png";
+        }
+
+        #endregion 判断文件类型是否为WEB格式图片
+
+        #region 裁剪图片
+
+        /// <summary>
+        /// 裁剪图片 -- 用GDI+
+        /// </summary>
+        /// <param name="b">原始Bitmap</param>
+        /// <param name="rec">裁剪区域</param>
+        /// <returns>剪裁后的Bitmap</returns>
+        public static Bitmap CutImage(this Bitmap b, Rectangle rec)
+        {
+            int w = b.Width;
+            int h = b.Height;
+            if (rec.X >= w || rec.Y >= h)
+            {
+                return null;
+            }
+
+            if (rec.X + rec.Width > w)
+            {
+                rec.Width = w - rec.X;
+            }
+
+            if (rec.Y + rec.Height > h)
+            {
+                rec.Height = h - rec.Y;
+            }
+
+            try
+            {
+                var bmppOut = new Bitmap(rec.Width, rec.Height, PixelFormat.Format24bppRgb);
+                using var g = Graphics.FromImage(bmppOut);
+                g.DrawImage(b, new Rectangle(0, 0, rec.Width, rec.Height), new Rectangle(rec.X, rec.Y, rec.Width, rec.Height), GraphicsUnit.Pixel);
+                return bmppOut;
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
+
+        #endregion 裁剪图片
+
+        #region 缩放图片
+
+        /// <summary>
+        ///  Resize图片
+        /// </summary>
+        /// <param name="bmpp">原始Bitmap </param>
+        /// <param name="newWidth">新的宽度</param>
+        /// <param name="newHeight">新的高度</param>
+        /// <returns>处理以后的图片</returns>
+        public static Bitmap ResizeImage(this Bitmap bmpp, int newWidth, int newHeight)
+        {
+            try
+            {
+                var b = new Bitmap(newWidth, newHeight);
+                using var g = Graphics.FromImage(b);
+
+                // 插值算法的质量
+                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                g.DrawImage(bmpp, new Rectangle(0, 0, newWidth, newHeight), new Rectangle(0, 0, bmpp.Width, bmpp.Height), GraphicsUnit.Pixel);
+                return b;
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
+
+        #endregion 缩放图片
+
+        #region 裁剪并缩放
+
+        /// <summary>
+        /// 裁剪并缩放
+        /// </summary>
+        /// <param name="bmpp">原始图片</param>
+        /// <param name="rec">裁剪的矩形区域</param>
+        /// <param name="newWidth">新的宽度</param>
+        /// <param name="newHeight">新的高度</param>
+        /// <returns>处理以后的图片</returns>
+        public static Bitmap CutAndResize(this Bitmap bmpp, Rectangle rec, int newWidth, int newHeight) => bmpp.CutImage(rec).ResizeImage(newWidth, newHeight);
+
+        #endregion 裁剪并缩放
+
+        #region 无损压缩图片
+
+        /// <summary>
+        /// 无损压缩图片
+        /// </summary>
+        /// <param name="sFile">原图片地址</param>
+        /// <param name="dFile">压缩后保存图片地址</param>
+        /// <param name="quality">压缩质量(数字越小压缩率越高)1-100</param>
+        /// <param name="size">压缩后图片的最大大小</param>
+        /// <param name="sfsc">是否是第一次调用</param>
+        /// <returns></returns>
+        public static bool CompressImage(string sFile, string dFile, byte quality = 90, int size = 1024, bool sfsc = true)
+        {
+            //如果是第一次调用,原始图像的大小小于要压缩的大小,则直接复制文件,并且返回true
+            var firstFileInfo = new FileInfo(sFile);
+            if (sfsc && firstFileInfo.Length < size * 1024)
+            {
+                firstFileInfo.CopyTo(dFile);
+                return true;
+            }
+
+            using Image iSource = Image.FromFile(sFile);
+            int dHeight = iSource.Height;
+            int dWidth = iSource.Width;
+            int sW, sH;
+
+            //按比例缩放
+            Size temSize = new Size(iSource.Width, iSource.Height);
+            if (temSize.Width > dHeight || temSize.Width > dWidth)
+            {
+                if (temSize.Width * dHeight > temSize.Width * dWidth)
+                {
+                    sW = dWidth;
+                    sH = dWidth * temSize.Height / temSize.Width;
+                }
+                else
+                {
+                    sH = dHeight;
+                    sW = temSize.Width * dHeight / temSize.Height;
+                }
+            }
+            else
+            {
+                sW = temSize.Width;
+                sH = temSize.Height;
+            }
+
+            using Bitmap bmpp = new Bitmap(dWidth, dHeight);
+            using Graphics g = Graphics.FromImage(bmpp);
+            g.Clear(Color.WhiteSmoke);
+            g.CompositingQuality = CompositingQuality.HighQuality;
+            g.SmoothingMode = SmoothingMode.HighQuality;
+            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
+            g.DrawImage(iSource, new Rectangle((dWidth - sW) / 2, (dHeight - sH) / 2, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel);
+
+            //以下代码为保存图片时,设置压缩质量
+            using var ep = new EncoderParameters();
+            using var eParam = new EncoderParameter(Encoder.Quality, new long[] { quality });
+            ep.Param[0] = eParam;
+            try
+            {
+                ImageCodecInfo[] arrayIci = ImageCodecInfo.GetImageEncoders();
+                ImageCodecInfo jpegIcIinfo = arrayIci.FirstOrDefault(t => t.FormatDescription.Equals("JPEG"));
+                if (jpegIcIinfo != null)
+                {
+                    bmpp.Save(dFile, jpegIcIinfo, ep);//dFile是压缩后的新路径
+                    FileInfo fi = new FileInfo(dFile);
+                    if (fi.Length > 1024 * size && quality > 10)
+                    {
+                        quality -= 10;
+                        CompressImage(sFile, dFile, quality, size, false);
+                    }
+                }
+                else
+                {
+                    bmpp.Save(dFile, iSource.RawFormat);
+                }
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 无损压缩图片
+        /// </summary>
+        /// <param name="src">原图片文件流</param>
+        /// <param name="dest">压缩后图片文件流</param>
+        /// <param name="quality">压缩质量(数字越小压缩率越高)1-100</param>
+        /// <param name="size">压缩后图片的最大大小</param>
+        /// <param name="sfsc">是否是第一次调用</param>
+        /// <returns></returns>
+        public static bool CompressImage(Stream src, Stream dest, byte quality = 90, int size = 1024, bool sfsc = true)
+        {
+            //如果是第一次调用,原始图像的大小小于要压缩的大小,则直接复制文件,并且返回true
+            if (sfsc && src.Length < size * 1024)
+            {
+                src.CopyTo(dest);
+                return true;
+            }
+
+            using Image iSource = Image.FromStream(src);
+            int dHeight = iSource.Height;
+            int dWidth = iSource.Width;
+            int sW, sH;
+
+            //按比例缩放
+            Size temSize = new Size(iSource.Width, iSource.Height);
+            if (temSize.Width > dHeight || temSize.Width > dWidth)
+            {
+                if ((temSize.Width * dHeight) > (temSize.Width * dWidth))
+                {
+                    sW = dWidth;
+                    sH = (dWidth * temSize.Height) / temSize.Width;
+                }
+                else
+                {
+                    sH = dHeight;
+                    sW = (temSize.Width * dHeight) / temSize.Height;
+                }
+            }
+            else
+            {
+                sW = temSize.Width;
+                sH = temSize.Height;
+            }
+
+            using Bitmap bmpp = new Bitmap(dWidth, dHeight);
+            using Graphics g = Graphics.FromImage(bmpp);
+            g.Clear(Color.WhiteSmoke);
+            g.CompositingQuality = CompositingQuality.HighQuality;
+            g.SmoothingMode = SmoothingMode.HighQuality;
+            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
+            g.DrawImage(iSource, new Rectangle((dWidth - sW) / 2, (dHeight - sH) / 2, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel);
+
+            //以下代码为保存图片时,设置压缩质量
+            using var ep = new EncoderParameters();
+            using var eParam = new EncoderParameter(Encoder.Quality, new long[] { quality });
+            ep.Param[0] = eParam;
+            try
+            {
+                ImageCodecInfo[] arrayIci = ImageCodecInfo.GetImageEncoders();
+                ImageCodecInfo jpegIcIinfo = arrayIci.FirstOrDefault(t => t.FormatDescription.Equals("JPEG"));
+                if (jpegIcIinfo != null)
+                {
+                    bmpp.Save(dest, jpegIcIinfo, ep);//dFile是压缩后的新路径
+                    if (dest.Length > 1024 * size && quality > 10)
+                    {
+                        quality -= 10;
+                        CompressImage(src, dest, quality, size, false);
+                    }
+                }
+                else
+                {
+                    bmpp.Save(dest, iSource.RawFormat);
+                }
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        #endregion 无损压缩图片
+
+        #region 缩略图
+
+        /// <summary>
+        /// 生成缩略图
+        /// </summary>
+        /// <param name="originalImage">原图</param>
+        /// <param name="thumbnailPath">缩略图路径(物理路径)</param>
+        /// <param name="width">缩略图宽度</param>
+        /// <param name="height">缩略图高度</param>
+        /// <param name="mode">生成缩略图的方式</param>
+        public static void MakeThumbnail(this Image originalImage, string thumbnailPath, int width, int height, ThumbnailCutMode mode)
+        {
+            using var bitmap = MakeThumbnail(originalImage, width, height, mode);
+            bitmap.Save(thumbnailPath, originalImage.RawFormat);
+        }
+
+        /// <summary>
+        /// 生成缩略图
+        /// </summary>
+        /// <param name="originalImage">原图</param>
+        /// <param name="width">缩略图宽度</param>
+        /// <param name="height">缩略图高度</param>
+        /// <param name="mode">生成缩略图的方式</param>
+        public static Image MakeThumbnail(this Image originalImage, int width, int height, ThumbnailCutMode mode)
+        {
+            int towidth = width;
+            int toheight = height;
+            int x = 0;
+            int y = 0;
+            int ow = originalImage.Width;
+            int oh = originalImage.Height;
+
+            switch (mode)
+            {
+                case ThumbnailCutMode.Fixed: //指定高宽缩放(可能变形)
+                    break;
+
+                case ThumbnailCutMode.LockWidth: //指定宽,高按比例
+                    toheight = originalImage.Height * width / originalImage.Width;
+                    break;
+
+                case ThumbnailCutMode.LockHeight: //指定高,宽按比例
+                    towidth = originalImage.Width * height / originalImage.Height;
+                    break;
+
+                case ThumbnailCutMode.LockHeightAndWidth: //指定高,宽按比例
+                    towidth = originalImage.Width * height / originalImage.Height;
+                    towidth = towidth > width ? width : towidth;
+                    toheight = originalImage.Height * towidth / originalImage.Width;
+                    toheight = toheight > height ? height : toheight;
+                    towidth = originalImage.Width * toheight / originalImage.Height;
+                    break;
+
+                case ThumbnailCutMode.Cut: //指定高宽裁减(不变形)
+                    if (originalImage.Width / (double)originalImage.Height > towidth / (double)toheight)
+                    {
+                        oh = originalImage.Height;
+                        ow = originalImage.Height * towidth / toheight;
+                        y = 0;
+                        x = (originalImage.Width - ow) / 2;
+                    }
+                    else
+                    {
+                        ow = originalImage.Width;
+                        oh = originalImage.Width * height / towidth;
+                        x = 0;
+                        y = (originalImage.Height - oh) / 2;
+                    }
+                    break;
+            }
+
+            //新建一个bmpp图片
+            Image bitmap = new Bitmap(towidth, toheight);
+
+            //新建一个画板
+            using Graphics g = Graphics.FromImage(bitmap);
+
+            //设置高质量插值法
+            g.InterpolationMode = InterpolationMode.High;
+
+            //设置高质量,低速度呈现平滑程度
+            g.SmoothingMode = SmoothingMode.HighQuality;
+
+            //清空画布并以透明背景色填充
+            g.Clear(Color.Transparent);
+
+            //在指定位置并且按指定大小绘制原图片的指定部分
+            //第一个:对哪张图片进行操作。
+            //二:画多么大。
+            //三:画那块区域。
+            g.DrawImage(originalImage, new Rectangle(0, 0, towidth, toheight), new Rectangle(x, y, ow, oh), GraphicsUnit.Pixel);
+
+            return bitmap;
+        }
+
+        #endregion 缩略图
+
+        #region 调整光暗
+
+        /// <summary>
+        /// 调整光暗
+        /// </summary>
+        /// <param name="source">原始图片</param>
+        /// <param name="width">原始图片的长度</param>
+        /// <param name="height">原始图片的高度</param>
+        /// <param name="val">增加或减少的光暗值</param>
+        public static Bitmap LDPic(this Bitmap source, int width, int height, int val)
+        {
+            Bitmap bmp = new Bitmap(width, height); //初始化一个记录经过处理后的图片对象
+            for (int x = 0; x < width; x++)
+            {
+                for (int y = 0; y < height; y++)
+                {
+                    var pixel = source.GetPixel(x, y);
+                    var resultR = pixel.R + val; //x、y是循环次数,后面三个是记录红绿蓝三个值的
+                    var resultG = pixel.G + val; //x、y是循环次数,后面三个是记录红绿蓝三个值的
+                    var resultB = pixel.B + val; //x、y是循环次数,后面三个是记录红绿蓝三个值的
+                    bmp.SetPixel(x, y, Color.FromArgb(resultR, resultG, resultB)); //绘图
+                }
+            }
+
+            return bmp;
+        }
+
+        #endregion 调整光暗
+
+        #region 反色处理
+
+        /// <summary>
+        /// 反色处理
+        /// </summary>
+        /// <param name="source">原始图片</param>
+        /// <param name="width">原始图片的长度</param>
+        /// <param name="height">原始图片的高度</param>
+        public static Bitmap RePic(this Bitmap source, int width, int height)
+        {
+            var bmp = new Bitmap(width, height); //初始化一个记录处理后的图片的对象
+            for (var x = 0; x < width; x++)
+            {
+                for (var y = 0; y < height; y++)
+                {
+                    var pixel = source.GetPixel(x, y);
+                    var resultR = 255 - pixel.R;
+                    var resultG = 255 - pixel.G;
+                    var resultB = 255 - pixel.B;
+                    bmp.SetPixel(x, y, Color.FromArgb(resultR, resultG, resultB)); //绘图
+                }
+            }
+
+            return bmp;
+        }
+
+        #endregion 反色处理
+
+        #region 浮雕处理
+
+        /// <summary>
+        /// 浮雕处理
+        /// </summary>
+        /// <param name="oldBitmap">原始图片</param>
+        /// <param name="width">原始图片的长度</param>
+        /// <param name="height">原始图片的高度</param>
+        public static Bitmap Relief(this Bitmap oldBitmap, int width, int height)
+        {
+            var newBitmap = new Bitmap(width, height);
+            for (int x = 0; x < width - 1; x++)
+            {
+                for (int y = 0; y < height - 1; y++)
+                {
+                    var color1 = oldBitmap.GetPixel(x, y);
+                    var color2 = oldBitmap.GetPixel(x + 1, y + 1);
+                    var r = Math.Abs(color1.R - color2.R + 128);
+                    var g = Math.Abs(color1.G - color2.G + 128);
+                    var b = Math.Abs(color1.B - color2.B + 128);
+                    if (r > 255) r = 255;
+                    if (r < 0) r = 0;
+                    if (g > 255) g = 255;
+                    if (g < 0) g = 0;
+                    if (b > 255) b = 255;
+                    if (b < 0) b = 0;
+                    newBitmap.SetPixel(x, y, Color.FromArgb(r, g, b));
+                }
+            }
+
+            return newBitmap;
+        }
+
+        #endregion 浮雕处理
+
+        #region 拉伸图片
+
+        /// <summary>
+        /// 拉伸图片
+        /// </summary>
+        /// <param name="bmpp">原始图片</param>
+        /// <param name="newW">新的宽度</param>
+        /// <param name="newH">新的高度</param>
+        public static async Task<Bitmap> ResizeImageAsync(this Bitmap bmpp, int newW, int newH)
+        {
+            try
+            {
+                Bitmap bap = new Bitmap(newW, newH);
+                return await Task.Run(() =>
+                {
+                    using Graphics g = Graphics.FromImage(bap);
+                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                    g.DrawImage(bap, new Rectangle(0, 0, newW, newH), new Rectangle(0, 0, bap.Width, bap.Height), GraphicsUnit.Pixel);
+                    return bap;
+                }).ConfigureAwait(false);
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
+
+        #endregion 拉伸图片
+
+        #region 滤色处理
+
+        /// <summary>
+        /// 滤色处理
+        /// </summary>
+        /// <param name="source">原始图片</param>
+        /// <param name="width">原始图片的长度</param>
+        /// <param name="height">原始图片的高度</param>
+        public static Bitmap FilPic(this Bitmap source, int width, int height)
+        {
+            var bmp = new Bitmap(width, height);
+            for (var x = 0; x < width; x++)
+            {
+                int y;
+                for (y = 0; y < height; y++)
+                {
+                    var pixel = source.GetPixel(x, y);
+                    bmp.SetPixel(x, y, Color.FromArgb(0, pixel.G, pixel.B)); //绘图
+                }
+            }
+
+            return bmp;
+        }
+
+        #endregion 滤色处理
+
+        #region 左右翻转
+
+        /// <summary>
+        /// 左右翻转
+        /// </summary>
+        /// <param name="source">原始图片</param>
+        /// <param name="width">原始图片的长度</param>
+        /// <param name="height">原始图片的高度</param>
+        public static Bitmap RevPicLR(this Bitmap source, int width, int height)
+        {
+            var bmp = new Bitmap(width, height);
+
+            //x,y是循环次数,z是用来记录像素点的x坐标的变化的
+            for (var y = height - 1; y >= 0; y--)
+            {
+                int x; //x,y是循环次数,z是用来记录像素点的x坐标的变化的
+                int z; //x,y是循环次数,z是用来记录像素点的x坐标的变化的
+                for (x = width - 1, z = 0; x >= 0; x--)
+                {
+                    var pixel = source.GetPixel(x, y);
+                    bmp.SetPixel(z++, y, Color.FromArgb(pixel.R, pixel.G, pixel.B)); //绘图
+                }
+            }
+
+            return bmp;
+        }
+
+        #endregion 左右翻转
+
+        #region 上下翻转
+
+        /// <summary>
+        /// 上下翻转
+        /// </summary>
+        /// <param name="source">原始图片</param>
+        /// <param name="width">原始图片的长度</param>
+        /// <param name="height">原始图片的高度</param>
+        public static Bitmap RevPicUD(this Bitmap source, int width, int height)
+        {
+            var bmp = new Bitmap(width, height);
+            for (var x = 0; x < width; x++)
+            {
+                int y;
+                int z;
+                for (y = height - 1, z = 0; y >= 0; y--)
+                {
+                    var pixel = source.GetPixel(x, y);
+                    bmp.SetPixel(x, z++, Color.FromArgb(pixel.R, pixel.G, pixel.B)); //绘图
+                }
+            }
+
+            return bmp;
+        }
+
+        #endregion 上下翻转
+
+        #region 灰度化
+
+        /// <summary>
+        /// 色彩灰度化
+        /// </summary>
+        /// <param name="c">输入颜色</param>
+        /// <returns>输出颜色</returns>
+        public static Color Gray(this Color c)
+        {
+            int rgb = Convert.ToInt32(0.3 * c.R + 0.59 * c.G + 0.11 * c.B);
+            return Color.FromArgb(rgb, rgb, rgb);
+        }
+
+        #endregion 灰度化
+
+        #region 转换为黑白图片
+
+        /// <summary>
+        /// 转换为黑白图片
+        /// </summary>
+        /// <param name="source">要进行处理的图片</param>
+        /// <param name="width">图片的长度</param>
+        /// <param name="height">图片的高度</param>
+        public static Bitmap BWPic(this Bitmap source, int width, int height)
+        {
+            var bmp = new Bitmap(width, height);
+            for (var x = 0; x < width; x++)
+            {
+                for (var y = 0; y < height; y++)
+                {
+                    var pixel = source.GetPixel(x, y);
+                    var result = (pixel.R + pixel.G + pixel.B) / 3; //记录处理后的像素值
+                    bmp.SetPixel(x, y, Color.FromArgb(result, result, result));
+                }
+            }
+
+            return bmp;
+        }
+
+        #endregion 转换为黑白图片
+
+        #region 获取图片中的各帧
+
+        /// <summary>
+        /// 获取gif图片中的各帧
+        /// </summary>
+        /// <param name="gif">源gif</param>
+        /// <param name="pSavedPath">保存路径</param>
+        public static void GetFrames(this Image gif, string pSavedPath)
+        {
+            var fd = new FrameDimension(gif.FrameDimensionsList[0]);
+            int count = gif.GetFrameCount(fd); //获取帧数(gif图片可能包含多帧,其它格式图片一般仅一帧)
+            for (int i = 0; i < count; i++) //以Jpeg格式保存各帧
+            {
+                gif.SelectActiveFrame(fd, i);
+                gif.Save(pSavedPath + "\\frame_" + i + ".jpg", ImageFormat.Jpeg);
+            }
+        }
+
+        #endregion 获取图片中的各帧
+
+        /// <summary>
+        /// 将dataUri保存为图片
+        /// </summary>
+        /// <param name="source">dataUri数据源</param>
+        /// <returns></returns>
+        /// <exception cref="Exception">操作失败。</exception>
+        public static Bitmap SaveDataUriAsImageFile(this string source)
+        {
+            string strbase64 = source.Substring(source.IndexOf(',') + 1).Trim('\0');
+            byte[] arr = Convert.FromBase64String(strbase64);
+            using var ms = new MemoryStream(arr);
+            using var bmpp = new Bitmap(ms);
+
+            //新建第二个bitmap类型的bmpp2变量。
+            var bmpp2 = new Bitmap(bmpp, bmpp.Width, bmpp.Height);
+            using var draw = Graphics.FromImage(bmpp2);
+            draw.DrawImage(bmpp, 0, 0, bmpp.Width, bmpp.Height);
+            return bmpp2;
+        }
+    }
+}

+ 99 - 0
Masuit.Tools.Net45/Media/ImageWatermarker.cs

@@ -0,0 +1,99 @@
+using System;
+using System.Drawing;
+using System.Drawing.Text;
+using System.IO;
+
+namespace Masuit.Tools.Media
+{
+    public class ImageWatermarker
+    {
+        /// <summary>
+        /// 是否跳过小缩略图
+        /// </summary>
+        public bool SkipWatermarkForSmallImages { get; set; }
+
+        /// <summary>
+        /// 小图像素大小
+        /// </summary>
+        public int SmallImagePixelsThreshold { get; set; }
+
+        private readonly Stream _stream;
+
+        public ImageWatermarker(Stream originStream)
+        {
+            _stream = originStream;
+        }
+
+        /// <summary>
+        /// 添加水印
+        /// </summary>
+        /// <param name="watermarkText">水印文字</param>
+        /// <param name="color">水印颜色</param>
+        /// <param name="watermarkPosition">水印位置</param>
+        /// <param name="textPadding">边距</param>
+        /// <param name="fontSize">字体大小</param>
+        /// <param name="font">字体</param>
+        /// <param name="textAntiAlias">不提示的情况下使用抗锯齿标志符号位图来绘制每个字符。
+        ///    由于抗锯齿质量就越好。
+        ///    因为关闭了提示,词干宽度之间的差异可能非常明显。</param>
+        /// <returns></returns>
+        public MemoryStream AddWatermark(string watermarkText, Color color, WatermarkPosition watermarkPosition = WatermarkPosition.BottomRight, int textPadding = 10, int fontSize = 20, Font font = null, bool textAntiAlias = true)
+        {
+            using var img = Image.FromStream(_stream);
+            if (SkipWatermarkForSmallImages && (img.Height < Math.Sqrt(SmallImagePixelsThreshold) || img.Width < Math.Sqrt(SmallImagePixelsThreshold)))
+            {
+                return _stream.SaveAsMemoryStream();
+            }
+
+            using var graphic = Graphics.FromImage(img);
+            if (textAntiAlias)
+            {
+                graphic.TextRenderingHint = TextRenderingHint.AntiAlias;
+            }
+
+            using var brush = new SolidBrush(color);
+            if (img.Width / fontSize > 50)
+            {
+                fontSize = img.Width / 50;
+            }
+
+            using var f = font ?? new Font(FontFamily.GenericSansSerif, fontSize, FontStyle.Bold, GraphicsUnit.Pixel);
+            var textSize = graphic.MeasureString(watermarkText, f);
+            int x, y;
+            textPadding += (img.Width - 1000) / 100;
+            switch (watermarkPosition)
+            {
+                case WatermarkPosition.TopLeft:
+                    x = textPadding;
+                    y = textPadding;
+                    break;
+
+                case WatermarkPosition.TopRight:
+                    x = img.Width - (int)textSize.Width - textPadding;
+                    y = textPadding;
+                    break;
+
+                case WatermarkPosition.BottomLeft:
+                    x = textPadding;
+                    y = img.Height - (int)textSize.Height - textPadding;
+                    break;
+
+                case WatermarkPosition.BottomRight:
+                    x = img.Width - (int)textSize.Width - textPadding;
+                    y = img.Height - (int)textSize.Height - textPadding;
+                    break;
+
+                default:
+                    x = textPadding;
+                    y = textPadding;
+                    break;
+            }
+
+            graphic.DrawString(watermarkText, f, brush, new Point(x, y));
+            var ms = new MemoryStream();
+            img.Save(ms, img.RawFormat);
+            ms.Position = 0;
+            return ms;
+        }
+    }
+}

+ 1 - 1
Masuit.Tools.Net45/package.nuspec

@@ -2,7 +2,7 @@
 <package>
   <metadata>
     <id>Masuit.Tools.Net45</id>
-    <version>2.4.9.4</version>
+    <version>2.5</version>
     <title>Masuit.Tools</title>
     <authors>懒得勤快</authors>
     <owners>masuit.com</owners>

+ 1 - 1
Masuit.Tools.NoSQL.MongoDBClient/Masuit.Tools.NoSQL.MongoDBClient.csproj

@@ -38,7 +38,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="MongoDB.Driver" Version="2.16.0" />
+    <PackageReference Include="MongoDB.Driver" Version="2.16.1" />
   </ItemGroup>
 
 </Project>

+ 9 - 0
Masuit.Tools/Masuit.Tools.csproj

@@ -106,6 +106,9 @@
     <Compile Include="..\Masuit.Tools.Abstractions\Strings\NumberFormater.cs">
       <Link>Strings\NumberFormater.cs</Link>
     </Compile>
+    <Compile Include="..\Masuit.Tools.Abstractions\Strings\SimHash.cs">
+      <Link>Strings\SimHash.cs</Link>
+    </Compile>
     <Compile Include="..\Masuit.Tools.Abstractions\Strings\Template.cs">
       <Link>Strings\Template.cs</Link>
     </Compile>
@@ -177,6 +180,12 @@
     <PackageReference Include="SharpCompress">
       <Version>0.32.1</Version>
     </PackageReference>
+    <PackageReference Include="SixLabors.ImageSharp">
+      <Version>2.1.3</Version>
+    </PackageReference>
+    <PackageReference Include="SixLabors.ImageSharp.Drawing">
+      <Version>1.0.0-beta14</Version>
+    </PackageReference>
     <PackageReference Include="StackExchange.Redis">
       <Version>2.6.45</Version>
     </PackageReference>

+ 1 - 1
Masuit.Tools/package.nuspec

@@ -2,7 +2,7 @@
 <package>
   <metadata>
     <id>Masuit.Tools.Net</id>
-    <version>2.4.9.4</version>
+    <version>2.5</version>
     <title>Masuit.Tools</title>
     <authors>懒得勤快</authors>
     <owners>masuit.com</owners>

部分文件因为文件数量过多而无法显示