ImageSharpTransformer.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. using SixLabors.ImageSharp;
  2. using SixLabors.ImageSharp.Formats;
  3. using SixLabors.ImageSharp.PixelFormats;
  4. using SixLabors.ImageSharp.Processing;
  5. using SixLabors.ImageSharp.Processing.Processors.Transforms;
  6. using System.IO;
  7. namespace Masuit.Tools.Media;
  8. /// <summary>
  9. /// 使用ImageSharp进行图像变换
  10. /// </summary>
  11. public class ImageSharpTransformer : IImageTransformer
  12. {
  13. #if NET6_0_OR_GREATER
  14. public byte[] TransformImage(Stream stream, int width, int height)
  15. {
  16. var decoderOptions = new DecoderOptions
  17. {
  18. TargetSize = new Size(160),
  19. SkipMetadata = true,
  20. };
  21. using var image = Image.Load<L8>(decoderOptions, stream);
  22. image.Mutate(x => x.Resize(new ResizeOptions()
  23. {
  24. Size = new Size
  25. {
  26. Width = width,
  27. Height = height
  28. },
  29. Mode = ResizeMode.Stretch,
  30. Sampler = new BicubicResampler()
  31. }));
  32. image.DangerousTryGetSinglePixelMemory(out var pixelSpan);
  33. var pixelArray = pixelSpan.ToArray();
  34. var pixelCount = width * height;
  35. var bytes = new byte[pixelCount];
  36. for (var i = 0; i < pixelCount; i++)
  37. {
  38. bytes[i] = pixelArray[i].PackedValue;
  39. }
  40. return bytes;
  41. }
  42. #else
  43. public byte[] TransformImage(Stream stream, int width, int height)
  44. {
  45. using var image = Image.Load<L8>(stream);
  46. image.Mutate(x => x.Resize(new ResizeOptions()
  47. {
  48. Size = new Size
  49. {
  50. Width = width,
  51. Height = height
  52. },
  53. Mode = ResizeMode.Stretch,
  54. Sampler = new BicubicResampler()
  55. }));
  56. image.DangerousTryGetSinglePixelMemory(out var pixelSpan);
  57. var pixelArray = pixelSpan.ToArray();
  58. var pixelCount = width * height;
  59. var bytes = new byte[pixelCount];
  60. for (var i = 0; i < pixelCount; i++)
  61. {
  62. bytes[i] = pixelArray[i].PackedValue;
  63. }
  64. return bytes;
  65. }
  66. #endif
  67. public byte[] TransformImage(Image<L8> image, int width, int height)
  68. {
  69. using var clone = image.Clone(x => x.Resize(new ResizeOptions()
  70. {
  71. Size = new Size
  72. {
  73. Width = width,
  74. Height = height
  75. },
  76. Mode = ResizeMode.Stretch,
  77. Sampler = new BicubicResampler()
  78. }));
  79. clone.DangerousTryGetSinglePixelMemory(out var pixelSpan);
  80. var pixelArray = pixelSpan.ToArray();
  81. var pixelCount = width * height;
  82. var bytes = new byte[pixelCount];
  83. for (var i = 0; i < pixelCount; i++)
  84. {
  85. bytes[i] = pixelArray[i].PackedValue;
  86. }
  87. return bytes;
  88. }
  89. public byte[,] GetPixelData(Image<L8> image, int width, int height)
  90. {
  91. using var clone = image.Clone(x => x.Resize(new ResizeOptions()
  92. {
  93. Size = new Size
  94. {
  95. Width = width,
  96. Height = height
  97. },
  98. Mode = ResizeMode.Stretch,
  99. Sampler = new BicubicResampler()
  100. }));
  101. var grayscalePixels = new byte[width, height];
  102. clone.ProcessPixelRows(accessor =>
  103. {
  104. for (int y = 0; y < width; y++)
  105. {
  106. var row = accessor.GetRowSpan(y);
  107. for (int x = 0; x < height; x++)
  108. {
  109. var pixel = row[x];
  110. grayscalePixels[y, x] = pixel.PackedValue;
  111. }
  112. }
  113. });
  114. return grayscalePixels;
  115. }
  116. }
  117. public static class ImageHashExt
  118. {
  119. private static readonly ImageHasher Hasher = new();
  120. /// <summary>
  121. /// 使用平均值算法计算图像的64位哈希
  122. /// </summary>
  123. /// <param name="image">读取到的图片流</param>
  124. /// <returns>64位hash值</returns>
  125. public static ulong AverageHash64(this Image image)
  126. {
  127. return Hasher.AverageHash64(image);
  128. }
  129. /// <summary>
  130. /// 使用平均值算法计算图像的64位哈希
  131. /// </summary>
  132. /// <param name="image">读取到的图片流</param>
  133. /// <returns>64位hash值</returns>
  134. public static ulong AverageHash64(this Image<L8> image)
  135. {
  136. return Hasher.AverageHash64(image);
  137. }
  138. /// <summary>
  139. /// 使用中值算法计算给定图像的64位哈希
  140. /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  141. /// </summary>
  142. /// <param name="image">读取到的图片流</param>
  143. /// <returns>64位hash值</returns>
  144. public static ulong MedianHash64(this Image image)
  145. {
  146. return Hasher.MedianHash64(image);
  147. }
  148. /// <summary>
  149. /// 使用中值算法计算给定图像的64位哈希
  150. /// 将图像转换为8x8灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  151. /// </summary>
  152. /// <param name="image">读取到的图片流</param>
  153. /// <returns>64位hash值</returns>
  154. public static ulong MedianHash64(this Image<L8> image)
  155. {
  156. return Hasher.MedianHash64(image);
  157. }
  158. /// <summary>
  159. /// 使用中值算法计算给定图像的256位哈希
  160. /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  161. /// </summary>
  162. /// <param name="image">读取到的图片流</param>
  163. /// <returns>256位hash值,生成一个4长度的数组返回</returns>
  164. public static ulong[] MedianHash256(this Image image)
  165. {
  166. return Hasher.MedianHash256(image);
  167. }
  168. /// <summary>
  169. /// 使用中值算法计算给定图像的256位哈希
  170. /// 将图像转换为16x16的灰度图像,从中查找中值像素值,然后在结果哈希中将值大于中值的所有像素标记为1。与基于平均值的实现相比,更能抵抗非线性图像编辑。
  171. /// </summary>
  172. /// <param name="image">读取到的图片流</param>
  173. /// <returns>256位hash值,生成一个4长度的数组返回</returns>
  174. public static ulong[] MedianHash256(this Image<L8> image)
  175. {
  176. return Hasher.MedianHash256(image);
  177. }
  178. /// <summary>
  179. /// 使用差分哈希算法计算图像的64位哈希。
  180. /// </summary>
  181. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  182. /// <param name="image">读取到的图片流</param>
  183. /// <returns>64位hash值</returns>
  184. public static ulong DifferenceHash64(this Image image)
  185. {
  186. return Hasher.DifferenceHash64(image);
  187. }
  188. /// <summary>
  189. /// 使用差分哈希算法计算图像的64位哈希。
  190. /// </summary>
  191. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  192. /// <param name="image">读取到的图片流</param>
  193. /// <returns>64位hash值</returns>
  194. public static ulong DifferenceHash64(this Image<L8> image)
  195. {
  196. return Hasher.DifferenceHash64(image);
  197. }
  198. /// <summary>
  199. /// 使用差分哈希算法计算图像的64位哈希。
  200. /// </summary>
  201. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  202. /// <param name="image">读取到的图片流</param>
  203. /// <returns>256位hash值</returns>
  204. public static ulong[] DifferenceHash256(this Image image)
  205. {
  206. return Hasher.DifferenceHash256(image);
  207. }
  208. /// <summary>
  209. /// 使用差分哈希算法计算图像的64位哈希。
  210. /// </summary>
  211. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  212. /// <param name="image">读取到的图片流</param>
  213. /// <returns>256位hash值</returns>
  214. public static ulong[] DifferenceHash256(this Image<L8> image)
  215. {
  216. return Hasher.DifferenceHash256(image);
  217. }
  218. /// <summary>
  219. /// 使用32分辨率精度DCT算法计算图像的64位哈希
  220. /// </summary>
  221. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  222. /// <param name="image">读取到的图片流</param>
  223. /// <returns>64位hash值</returns>
  224. public static ulong DctHash(this Image image)
  225. {
  226. return Hasher.DctHash(image);
  227. }
  228. /// <summary>
  229. /// 使用32分辨率精度DCT算法计算图像的64位哈希
  230. /// </summary>
  231. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  232. /// <param name="image">读取到的图片流</param>
  233. /// <returns>64位hash值</returns>
  234. public static ulong DctHash(this Image<L8> image)
  235. {
  236. return Hasher.DctHash(image);
  237. }
  238. /// <summary>
  239. /// 使用64分辨率精度DCT算法计算图像的64位哈希
  240. /// </summary>
  241. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  242. /// <param name="image">读取到的图片流</param>
  243. /// <returns>64位hash值</returns>
  244. public static ulong DctHash64(this Image image)
  245. {
  246. return Hasher.DctHash64(image);
  247. }
  248. /// <summary>
  249. /// 使用64分辨率精度DCT算法计算图像的64位哈希
  250. /// </summary>
  251. /// <see cref="https://segmentfault.com/a/1190000038308093"/>
  252. /// <param name="image">读取到的图片流</param>
  253. /// <returns>64位hash值</returns>
  254. public static ulong DctHash64(this Image<L8> image)
  255. {
  256. return Hasher.DctHash64(image);
  257. }
  258. /// <summary>
  259. /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
  260. /// </summary>
  261. /// <param name="image1">图像1</param>
  262. /// <param name="image2">图像2</param>
  263. /// <param name="algorithm">对比算法</param>
  264. /// <returns>相似度范围:[0,1]</returns>
  265. public static float Compare(this Image image1, Image image2, ImageHashAlgorithm algorithm = ImageHashAlgorithm.Difference)
  266. {
  267. return algorithm switch
  268. {
  269. ImageHashAlgorithm.Average => ImageHasher.Compare(Hasher.AverageHash64(image1), Hasher.AverageHash64(image2)),
  270. ImageHashAlgorithm.Medium => ImageHasher.Compare(Hasher.MedianHash256(image1), Hasher.MedianHash256(image2)),
  271. ImageHashAlgorithm.Difference => ImageHasher.Compare(Hasher.DifferenceHash256(image1), Hasher.DifferenceHash256(image2)),
  272. ImageHashAlgorithm.DCT32 => ImageHasher.Compare(Hasher.DctHash(image1), Hasher.DctHash(image2)),
  273. ImageHashAlgorithm.DCT64 => ImageHasher.Compare(Hasher.DctHash64(image1), Hasher.DctHash64(image2)),
  274. _ => throw new ArgumentOutOfRangeException(nameof(algorithm), algorithm, null)
  275. };
  276. }
  277. /// <summary>
  278. /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
  279. /// </summary>
  280. /// <param name="image1">图像1</param>
  281. /// <param name="image2">图像2</param>
  282. /// <param name="algorithm">对比算法</param>
  283. /// <returns>相似度范围:[0,1]</returns>
  284. public static float Compare(this Image<L8> image1, Image<L8> image2, ImageHashAlgorithm algorithm = ImageHashAlgorithm.Difference)
  285. {
  286. return algorithm switch
  287. {
  288. ImageHashAlgorithm.Average => ImageHasher.Compare(Hasher.AverageHash64(image1), Hasher.AverageHash64(image2)),
  289. ImageHashAlgorithm.Medium => ImageHasher.Compare(Hasher.MedianHash256(image1), Hasher.MedianHash256(image2)),
  290. ImageHashAlgorithm.Difference => ImageHasher.Compare(Hasher.DifferenceHash256(image1), Hasher.DifferenceHash256(image2)),
  291. ImageHashAlgorithm.DCT32 => ImageHasher.Compare(Hasher.DctHash(image1), Hasher.DctHash(image2)),
  292. ImageHashAlgorithm.DCT64 => ImageHasher.Compare(Hasher.DctHash64(image1), Hasher.DctHash64(image2)),
  293. _ => throw new ArgumentOutOfRangeException(nameof(algorithm), algorithm, null)
  294. };
  295. }
  296. /// <summary>
  297. /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
  298. /// </summary>
  299. /// <param name="image1">图像1的hash</param>
  300. /// <param name="image2path">图像2的路径</param>
  301. /// <param name="algorithm">对比算法</param>
  302. /// <returns>相似度范围:[0,1]</returns>
  303. public static float Compare(this Image image1, string image2path, ImageHashAlgorithm algorithm = ImageHashAlgorithm.Difference)
  304. {
  305. return algorithm switch
  306. {
  307. ImageHashAlgorithm.Average => ImageHasher.Compare(Hasher.AverageHash64(image1), Hasher.AverageHash64(image2path)),
  308. ImageHashAlgorithm.Medium => ImageHasher.Compare(Hasher.MedianHash256(image1), Hasher.MedianHash256(image2path)),
  309. ImageHashAlgorithm.Difference => ImageHasher.Compare(Hasher.DifferenceHash256(image1), Hasher.DifferenceHash256(image2path)),
  310. ImageHashAlgorithm.DCT32 => ImageHasher.Compare(Hasher.DctHash(image1), Hasher.DctHash(image2path)),
  311. ImageHashAlgorithm.DCT64 => ImageHasher.Compare(Hasher.DctHash64(image1), Hasher.DctHash64(image2path)),
  312. _ => throw new ArgumentOutOfRangeException(nameof(algorithm), algorithm, null)
  313. };
  314. }
  315. /// <summary>
  316. /// 使用汉明距离比较两幅图像的哈希值。结果1表示图像完全相同,而结果0表示图像完全不同。
  317. /// </summary>
  318. /// <param name="image1">图像1的hash</param>
  319. /// <param name="image2path">图像2的路径</param>
  320. /// <param name="algorithm">对比算法</param>
  321. /// <returns>相似度范围:[0,1]</returns>
  322. public static float Compare(this Image<L8> image1, string image2path, ImageHashAlgorithm algorithm = ImageHashAlgorithm.Difference)
  323. {
  324. return algorithm switch
  325. {
  326. ImageHashAlgorithm.Average => ImageHasher.Compare(Hasher.AverageHash64(image1), Hasher.AverageHash64(image2path)),
  327. ImageHashAlgorithm.Medium => ImageHasher.Compare(Hasher.MedianHash256(image1), Hasher.MedianHash256(image2path)),
  328. ImageHashAlgorithm.Difference => ImageHasher.Compare(Hasher.DifferenceHash256(image1), Hasher.DifferenceHash256(image2path)),
  329. ImageHashAlgorithm.DCT32 => ImageHasher.Compare(Hasher.DctHash(image1), Hasher.DctHash(image2path)),
  330. ImageHashAlgorithm.DCT64 => ImageHasher.Compare(Hasher.DctHash64(image1), Hasher.DctHash64(image2path)),
  331. _ => throw new ArgumentOutOfRangeException(nameof(algorithm), algorithm, null)
  332. };
  333. }
  334. }
  335. public enum ImageHashAlgorithm
  336. {
  337. /// <summary>
  338. /// 均值算法
  339. /// </summary>
  340. Average,
  341. /// <summary>
  342. /// 中值算法
  343. /// </summary>
  344. Medium,
  345. /// <summary>
  346. /// 差异算法
  347. /// </summary>
  348. Difference,
  349. /// <summary>
  350. /// 32分辨率精度DCT算法
  351. /// </summary>
  352. DCT32,
  353. /// <summary>
  354. /// 64分辨率精度DCT算法
  355. /// </summary>
  356. DCT64,
  357. }