ImageBorderRemover.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. using Masuit.Tools.Systems;
  2. using SixLabors.ImageSharp;
  3. using SixLabors.ImageSharp.PixelFormats;
  4. using SixLabors.ImageSharp.Processing;
  5. using Color = System.Drawing.Color;
  6. // ReSharper disable AccessToDisposedClosure
  7. namespace Masuit.Tools.Media;
  8. /// <summary>
  9. /// 图像边框移除器
  10. /// </summary>
  11. public class ImageBorderRemover
  12. {
  13. /// <summary>
  14. /// 容差模式
  15. /// </summary>
  16. private ToleranceMode ToleranceMode { get; }
  17. private int CroppedBorderCount { get; }
  18. /// <summary>
  19. ///
  20. /// </summary>
  21. /// <param name="mode">容差模式</param>
  22. /// <param name="croppedBorderCount">达到边框个数则裁剪</param>
  23. public ImageBorderRemover(ToleranceMode mode, int croppedBorderCount = 2)
  24. {
  25. ToleranceMode = mode;
  26. CroppedBorderCount = croppedBorderCount;
  27. }
  28. /// <summary>
  29. /// 检测图片边框信息(支持多色边框)
  30. /// </summary>
  31. /// <param name="imagePath">图片路径</param>
  32. /// <param name="tolerance">颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间)</param>
  33. /// <param name="maxLayers">最大检测边框层数,默认3</param>
  34. /// <param name="useDownscaling">是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪</param>
  35. /// <param name="downscaleFactor">缩小采样比例(1-10),默认4</param>
  36. /// <returns>边框检测结果</returns>
  37. public BorderDetectionResult DetectBorders(string imagePath, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4)
  38. {
  39. using var image = Image.Load<Rgba32>(imagePath);
  40. return DetectBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor);
  41. }
  42. /// <summary>
  43. /// 检测图片边框信息(从已加载的图像)
  44. /// </summary>
  45. /// <param name="image">已加载的图像</param>
  46. /// <param name="tolerance">颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间)</param>
  47. /// <param name="maxLayers">最大检测边框层数,默认3</param>
  48. /// <param name="useDownscaling">是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪</param>
  49. /// <param name="downscaleFactor">缩小采样比例(1-10),默认4</param>
  50. /// <returns>边框检测结果</returns>
  51. public BorderDetectionResult DetectBorders(Image<Rgba32> image, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4)
  52. {
  53. var result = new BorderDetectionResult(CroppedBorderCount)
  54. {
  55. ImageWidth = image.Width,
  56. ImageHeight = image.Height,
  57. BorderColors = new List<Rgba32>(),
  58. BorderLayers = 0
  59. };
  60. byte toleranceValue = (byte)(tolerance * 2.55);
  61. // 使用多层边框检测算法
  62. var (top, bottom, left, right, layers, colors) = FindContentBordersWithLayers(image, toleranceValue, maxLayers, useDownscaling, downscaleFactor);
  63. // 设置内容边界
  64. result.ContentTop = top;
  65. result.ContentBottom = bottom;
  66. result.ContentLeft = left;
  67. result.ContentRight = right;
  68. result.BorderLayers = layers;
  69. result.BorderColors = colors;
  70. return result;
  71. }
  72. /// <summary>
  73. /// 自动移除图片的多层边框
  74. /// </summary>
  75. /// <param name="inputPath">输入图片路径</param>
  76. /// <param name="tolerance">颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间)</param>
  77. /// <param name="maxLayers">最大检测边框层数,默认3</param>
  78. /// <param name="useDownscaling">是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪</param>
  79. /// <param name="downscaleFactor">缩小采样比例(1-10),默认4</param>
  80. /// <returns>是否执行了裁剪操作</returns>
  81. public void RemoveBorders(string inputPath, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4)
  82. {
  83. RemoveBorders(inputPath, inputPath, tolerance, maxLayers, useDownscaling, downscaleFactor);
  84. }
  85. /// <summary>
  86. /// 自动移除图片的多层边框
  87. /// </summary>
  88. /// <param name="inputPath">输入图片路径</param>
  89. /// <param name="outputPath">输出图片路径</param>
  90. /// <param name="tolerance">颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间)</param>
  91. /// <param name="maxLayers">最大检测边框层数,默认3</param>
  92. /// <param name="useDownscaling">是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪</param>
  93. /// <param name="downscaleFactor">缩小采样比例(1-10),默认4</param>
  94. /// <returns>是否执行了裁剪操作</returns>
  95. public void RemoveBorders(string inputPath, string outputPath, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4)
  96. {
  97. using Image<Rgba32> image = Image.Load<Rgba32>(inputPath);
  98. var hasCropped = RemoveBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor);
  99. // 决定是否保存
  100. if (hasCropped)
  101. {
  102. image.Save(outputPath);
  103. }
  104. }
  105. /// <summary>
  106. /// 自动移除图片的多层边框
  107. /// </summary>
  108. /// <param name="input">输入图片路径</param>
  109. /// <param name="tolerance">颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间)</param>
  110. /// <param name="maxLayers">最大检测边框层数,默认3</param>
  111. /// <param name="useDownscaling">是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪</param>
  112. /// <param name="downscaleFactor">缩小采样比例(1-10),默认4</param>
  113. /// <returns>是否执行了裁剪操作</returns>
  114. public PooledMemoryStream RemoveBorders(Stream input, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4)
  115. {
  116. var format = Image.DetectFormat(input);
  117. input.Seek(0, SeekOrigin.Begin);
  118. Image<Rgba32> image = Image.Load<Rgba32>(input);
  119. RemoveBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor);
  120. var stream = new PooledMemoryStream();
  121. image.Save(stream, format);
  122. return stream;
  123. }
  124. /// <summary>
  125. /// 自动移除图片的多层边框
  126. /// </summary>
  127. /// <param name="image"></param>
  128. /// <param name="tolerance">颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间)</param>
  129. /// <param name="maxLayers">最大检测边框层数,默认3</param>
  130. /// <param name="useDownscaling">是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪</param>
  131. /// <param name="downscaleFactor">缩小采样比例(1-10),默认4</param>
  132. /// <returns>是否执行了裁剪操作</returns>
  133. public bool RemoveBorders(Image<Rgba32> image, int tolerance, int maxLayers, bool useDownscaling, int downscaleFactor)
  134. {
  135. // 保存原始尺寸用于比较
  136. int originalWidth = image.Width;
  137. int originalHeight = image.Height;
  138. // 使用多层检测方法获取边框信息
  139. var borderInfo = DetectBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor);
  140. bool hasCropped = false;
  141. if (borderInfo.CanBeCropped)
  142. {
  143. int newWidth = borderInfo.ContentRight - borderInfo.ContentLeft + 1;
  144. int newHeight = borderInfo.ContentBottom - borderInfo.ContentTop + 1;
  145. if (newWidth > 0 && newHeight > 0 && (newWidth != originalWidth || newHeight != originalHeight))
  146. {
  147. image.Mutate(x => x.Crop(new Rectangle(borderInfo.ContentLeft, borderInfo.ContentTop, newWidth, newHeight)));
  148. hasCropped = true;
  149. }
  150. }
  151. return hasCropped;
  152. }
  153. /// <summary>
  154. /// 查找内容边界(支持多层边框检测)
  155. /// </summary>
  156. private (int top, int bottom, int left, int right, int layers, List<Rgba32> colors) FindContentBordersWithLayers(Image<Rgba32> image, byte tolerance, int maxLayers, bool useDownscaling, int downscaleFactor)
  157. {
  158. // 如果启用缩小采样且图像足够大
  159. Image<Rgba32> workingImage;
  160. float scale = 1f;
  161. bool isDownscaled = false;
  162. if (useDownscaling && image.Width > 500 && image.Height > 500)
  163. {
  164. // 计算缩小尺寸
  165. int newWidth = image.Width / downscaleFactor;
  166. int newHeight = image.Height / downscaleFactor;
  167. scale = (float)image.Width / newWidth;
  168. // 创建缩小版本用于检测
  169. workingImage = image.Clone(ctx => ctx.Resize(newWidth, newHeight));
  170. isDownscaled = true;
  171. }
  172. else
  173. {
  174. workingImage = image;
  175. }
  176. int width = workingImage.Width;
  177. int height = workingImage.Height;
  178. int top = 0;
  179. int bottom = height - 1;
  180. int left = 0;
  181. int right = width - 1;
  182. int layers = 0;
  183. var borderColors = new List<Rgba32>();
  184. // 检测多层边框
  185. for (int layer = 0; layer < maxLayers; layer++)
  186. {
  187. bool borderFound = false;
  188. // 并行检测四个方向的边框层
  189. var results = new (int borderSize, Rgba32? color)[4];
  190. Parallel.Invoke(() =>
  191. {
  192. if (top < height / 2)
  193. {
  194. Rgba32? layerColor = null;
  195. int newTop = DetectLayerBorderTop(workingImage, top, bottom, left, right, tolerance, ref layerColor);
  196. results[0] = (newTop - top, layerColor);
  197. if (newTop > top) borderFound = true;
  198. top = newTop;
  199. }
  200. }, () =>
  201. {
  202. if (bottom > height / 2)
  203. {
  204. Rgba32? layerColor = null;
  205. int newBottom = DetectLayerBorderBottom(workingImage, top, bottom, left, right, tolerance, ref layerColor);
  206. results[1] = (newBottom - bottom, layerColor);
  207. if (newBottom < bottom) borderFound = true;
  208. bottom = newBottom;
  209. }
  210. }, () =>
  211. {
  212. if (left < width / 2)
  213. {
  214. Rgba32? layerColor = null;
  215. int newLeft = DetectLayerBorderLeft(workingImage, top, bottom, left, right, tolerance, ref layerColor);
  216. results[2] = (newLeft - left, layerColor);
  217. if (newLeft > left) borderFound = true;
  218. left = newLeft;
  219. }
  220. }, () =>
  221. {
  222. if (right > width / 2)
  223. {
  224. Rgba32? layerColor = null;
  225. int newRight = DetectLayerBorderRight(workingImage, top, bottom, left, right, tolerance, ref layerColor);
  226. results[3] = (newRight - right, layerColor);
  227. if (newRight < right) borderFound = true;
  228. right = newRight;
  229. }
  230. });
  231. // 收集检测到的边框颜色
  232. foreach (var (borderSize, color) in results)
  233. {
  234. if (color.HasValue && borderSize > 0)
  235. {
  236. borderColors.Add(color.Value);
  237. }
  238. }
  239. if (borderFound)
  240. {
  241. layers++;
  242. }
  243. else
  244. {
  245. break; // 没有检测到更多边框层
  246. }
  247. }
  248. // 如果是缩小采样版本,映射回原图坐标
  249. if (isDownscaled)
  250. {
  251. top = (int)(top * scale);
  252. bottom = (int)(bottom * scale);
  253. left = (int)(left * scale);
  254. right = (int)(right * scale);
  255. // 确保边界在图像范围内
  256. top = Clamp(top, 0, image.Height - 1);
  257. bottom = Clamp(bottom, top, image.Height - 1);
  258. left = Clamp(left, 0, image.Width - 1);
  259. right = Clamp(right, left, image.Width - 1);
  260. // 释放缩小图像
  261. workingImage.Dispose();
  262. }
  263. return (top, bottom, left, right, layers, borderColors);
  264. }
  265. private static int Clamp(int value, int min, int max)
  266. {
  267. return value < min ? min : value > max ? max : value;
  268. }
  269. /// <summary>
  270. /// 检测顶部边框层(优化版)
  271. /// </summary>
  272. private int DetectLayerBorderTop(Image<Rgba32> image, int currentTop, int currentBottom, int currentLeft, int currentRight, byte tolerance, ref Rgba32? borderColor)
  273. {
  274. int newTop = currentTop;
  275. Rgba32? detectedColor = null;
  276. // 使用采样检测代替全行扫描
  277. int sampleCount = Math.Min(50, currentRight - currentLeft + 1);
  278. int stepX = Math.Max(1, (currentRight - currentLeft) / sampleCount);
  279. // 从当前顶部开始向下扫描
  280. for (int y = currentTop; y <= currentBottom; y++)
  281. {
  282. Rgba32? rowColor = null;
  283. bool isUniform = true;
  284. // 采样检查行是否统一颜色
  285. for (int x = currentLeft; x <= currentRight; x += stepX)
  286. {
  287. if (!rowColor.HasValue)
  288. {
  289. rowColor = image[x, y];
  290. continue;
  291. }
  292. if (!IsSimilarColor(image[x, y], rowColor.Value, tolerance))
  293. {
  294. isUniform = false;
  295. break;
  296. }
  297. }
  298. // 如果是统一颜色行
  299. if (isUniform && rowColor.HasValue)
  300. {
  301. // 第一行总是被认为是边框
  302. if (y == currentTop)
  303. {
  304. detectedColor = rowColor;
  305. newTop = y + 1;
  306. continue;
  307. }
  308. // 后续行必须与第一行颜色相似
  309. if (detectedColor.HasValue && IsSimilarColor(rowColor.Value, detectedColor.Value, tolerance))
  310. {
  311. newTop = y + 1;
  312. }
  313. else
  314. {
  315. break;
  316. }
  317. }
  318. else
  319. {
  320. break;
  321. }
  322. }
  323. if (newTop > currentTop)
  324. {
  325. borderColor = detectedColor;
  326. return newTop;
  327. }
  328. return currentTop;
  329. }
  330. /// <summary>
  331. /// 检测底部边框层(优化版)
  332. /// </summary>
  333. private int DetectLayerBorderBottom(Image<Rgba32> image, int currentTop, int currentBottom, int currentLeft, int currentRight, byte tolerance, ref Rgba32? borderColor)
  334. {
  335. int newBottom = currentBottom;
  336. Rgba32? detectedColor = null;
  337. // 使用采样检测代替全行扫描
  338. int sampleCount = Math.Min(50, currentRight - currentLeft + 1);
  339. int stepX = Math.Max(1, (currentRight - currentLeft) / sampleCount);
  340. // 从当前底部开始向上扫描
  341. for (int y = currentBottom; y >= currentTop; y--)
  342. {
  343. Rgba32? rowColor = null;
  344. bool isUniform = true;
  345. // 采样检查行是否统一颜色
  346. for (int x = currentLeft; x <= currentRight; x += stepX)
  347. {
  348. if (!rowColor.HasValue)
  349. {
  350. rowColor = image[x, y];
  351. continue;
  352. }
  353. if (!IsSimilarColor(image[x, y], rowColor.Value, tolerance))
  354. {
  355. isUniform = false;
  356. break;
  357. }
  358. }
  359. if (isUniform && rowColor.HasValue)
  360. {
  361. if (y == currentBottom)
  362. {
  363. detectedColor = rowColor;
  364. newBottom = y - 1;
  365. continue;
  366. }
  367. if (detectedColor.HasValue && IsSimilarColor(rowColor.Value, detectedColor.Value, tolerance))
  368. {
  369. newBottom = y - 1;
  370. }
  371. else
  372. {
  373. break;
  374. }
  375. }
  376. else
  377. {
  378. break;
  379. }
  380. }
  381. if (newBottom < currentBottom)
  382. {
  383. borderColor = detectedColor;
  384. return newBottom;
  385. }
  386. return currentBottom;
  387. }
  388. /// <summary>
  389. /// 检测左侧边框层(优化版)
  390. /// </summary>
  391. private int DetectLayerBorderLeft(Image<Rgba32> image, int currentTop, int currentBottom, int currentLeft, int currentRight, byte tolerance, ref Rgba32? borderColor)
  392. {
  393. int newLeft = currentLeft;
  394. Rgba32? detectedColor = null;
  395. // 使用采样检测代替全列扫描
  396. int sampleCount = Math.Min(50, currentBottom - currentTop + 1);
  397. int stepY = Math.Max(1, (currentBottom - currentTop) / sampleCount);
  398. // 从当前左侧开始向右扫描
  399. for (int x = currentLeft; x <= currentRight; x++)
  400. {
  401. Rgba32? colColor = null;
  402. bool isUniform = true;
  403. // 采样检查列是否统一颜色
  404. for (int y = currentTop; y <= currentBottom; y += stepY)
  405. {
  406. if (!colColor.HasValue)
  407. {
  408. colColor = image[x, y];
  409. continue;
  410. }
  411. if (!IsSimilarColor(image[x, y], colColor.Value, tolerance))
  412. {
  413. isUniform = false;
  414. break;
  415. }
  416. }
  417. if (isUniform && colColor.HasValue)
  418. {
  419. if (x == currentLeft)
  420. {
  421. detectedColor = colColor;
  422. newLeft = x + 1;
  423. continue;
  424. }
  425. if (detectedColor.HasValue && IsSimilarColor(colColor.Value, detectedColor.Value, tolerance))
  426. {
  427. newLeft = x + 1;
  428. }
  429. else
  430. {
  431. break;
  432. }
  433. }
  434. else
  435. {
  436. break;
  437. }
  438. }
  439. if (newLeft > currentLeft)
  440. {
  441. borderColor = detectedColor;
  442. return newLeft;
  443. }
  444. return currentLeft;
  445. }
  446. /// <summary>
  447. /// 检测右侧边框层(优化版)
  448. /// </summary>
  449. private int DetectLayerBorderRight(Image<Rgba32> image, int currentTop, int currentBottom, int currentLeft, int currentRight, byte tolerance, ref Rgba32? borderColor)
  450. {
  451. int newRight = currentRight;
  452. Rgba32? detectedColor = null;
  453. // 使用采样检测代替全列扫描
  454. int sampleCount = Math.Min(50, currentBottom - currentTop + 1);
  455. int stepY = Math.Max(1, (currentBottom - currentTop) / sampleCount);
  456. // 从当前右侧开始向左扫描
  457. for (int x = currentRight; x >= currentLeft; x--)
  458. {
  459. Rgba32? colColor = null;
  460. bool isUniform = true;
  461. // 采样检查列是否统一颜色
  462. for (int y = currentTop; y <= currentBottom; y += stepY)
  463. {
  464. if (!colColor.HasValue)
  465. {
  466. colColor = image[x, y];
  467. continue;
  468. }
  469. if (!IsSimilarColor(image[x, y], colColor.Value, tolerance))
  470. {
  471. isUniform = false;
  472. break;
  473. }
  474. }
  475. if (isUniform && colColor.HasValue)
  476. {
  477. if (x == currentRight)
  478. {
  479. detectedColor = colColor;
  480. newRight = x - 1;
  481. continue;
  482. }
  483. if (detectedColor.HasValue && IsSimilarColor(colColor.Value, detectedColor.Value, tolerance))
  484. {
  485. newRight = x - 1;
  486. }
  487. else
  488. {
  489. break;
  490. }
  491. }
  492. else
  493. {
  494. break;
  495. }
  496. }
  497. if (newRight < currentRight)
  498. {
  499. borderColor = detectedColor;
  500. return newRight;
  501. }
  502. return currentRight;
  503. }
  504. /// <summary>
  505. /// 颜色相似度比较(SIMD优化)
  506. /// </summary>
  507. private bool IsSimilarColor(Rgba32 color1, Rgba32 color2, byte tolerance)
  508. {
  509. switch (ToleranceMode)
  510. {
  511. case ToleranceMode.Channel:
  512. return CompareColors(color1, color2, tolerance, true);
  513. case ToleranceMode.DeltaE2000:
  514. return Color.FromArgb(color1.A, color1.R, color1.G, color1.B).CIE2000(Color.FromArgb(color2.A, color2.R, color2.G, color2.B)) <= tolerance;
  515. case ToleranceMode.DeltaE1976:
  516. return Color.FromArgb(color1.A, color1.R, color1.G, color1.B).CIE1976(Color.FromArgb(color2.A, color2.R, color2.G, color2.B)) <= tolerance;
  517. case ToleranceMode.DeltaE1994:
  518. return Color.FromArgb(color1.A, color1.R, color1.G, color1.B).CIE1994(Color.FromArgb(color2.A, color2.R, color2.G, color2.B)) <= tolerance;
  519. case ToleranceMode.DeltaECMC:
  520. return Color.FromArgb(color1.A, color1.R, color1.G, color1.B).CMC(Color.FromArgb(color2.A, color2.R, color2.G, color2.B)) <= tolerance;
  521. case ToleranceMode.EuclideanDistance:
  522. return CompareWithEuclideanDistance(color1, color2, tolerance, true);
  523. default:
  524. throw new ArgumentOutOfRangeException();
  525. }
  526. }
  527. /// <summary>
  528. /// 比较两个颜色是否在容差范围内相等
  529. /// </summary>
  530. /// <param name="color1">第一个颜色</param>
  531. /// <param name="color2">第二个颜色</param>
  532. /// <param name="tolerance">容差值 (0-255)</param>
  533. /// <param name="compareAlpha">是否比较Alpha通道</param>
  534. /// <returns>是否匹配</returns>
  535. private static bool CompareColors(Rgba32 color1, Rgba32 color2, int tolerance = 10, bool compareAlpha = false)
  536. {
  537. // 检查R通道
  538. if (Math.Abs(color1.R - color2.R) > tolerance)
  539. return false;
  540. // 检查G通道
  541. if (Math.Abs(color1.G - color2.G) > tolerance)
  542. return false;
  543. // 检查B通道
  544. if (Math.Abs(color1.B - color2.B) > tolerance)
  545. return false;
  546. // 可选检查Alpha通道
  547. if (compareAlpha && Math.Abs(color1.A - color2.A) > tolerance)
  548. return false;
  549. return true;
  550. }
  551. /// <summary>
  552. /// 使用欧几里得距离比较颜色
  553. /// </summary>
  554. /// <param name="color1">第一个颜色</param>
  555. /// <param name="color2">第二个颜色</param>
  556. /// <param name="maxDistance">最大允许距离 (0-442之间)</param>
  557. /// <param name="compareAlpha">是否包含Alpha通道</param>
  558. /// <returns>是否匹配</returns>
  559. private static bool CompareWithEuclideanDistance(Rgba32 color1, Rgba32 color2, double maxDistance = 20.0, bool compareAlpha = false)
  560. {
  561. // 计算各分量平方差之和
  562. double sum = Math.Pow(color1.R - color2.R, 2) + Math.Pow(color1.G - color2.G, 2) + Math.Pow(color1.B - color2.B, 2);
  563. // 可选添加Alpha通道
  564. if (compareAlpha)
  565. {
  566. sum += Math.Pow(color1.A - color2.A, 2);
  567. }
  568. // 计算欧几里得距离
  569. double distance = Math.Sqrt(sum);
  570. return distance <= maxDistance;
  571. }
  572. }
  573. public static class ImageBorderRemoverExt
  574. {
  575. /// <summary>
  576. /// 检测图片边框信息(从已加载的图像)
  577. /// </summary>
  578. /// <param name="image">已加载的图像</param>
  579. /// <param name="tolerance">颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间)</param>
  580. /// <param name="toleranceMode">容差模式</param>
  581. /// <param name="maxLayers">最大检测边框层数,默认3</param>
  582. /// <param name="useDownscaling">是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪</param>
  583. /// <param name="downscaleFactor">缩小采样比例(1-10),默认4</param>
  584. /// <returns>边框检测结果</returns>
  585. public static BorderDetectionResult DetectBorders(this Image<Rgba32> image, int tolerance, ToleranceMode toleranceMode, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4)
  586. {
  587. var remover = new ImageBorderRemover(toleranceMode);
  588. return remover.DetectBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor);
  589. }
  590. /// <summary>
  591. /// 自动移除图片的多层边框(仅当至少有两边存在边框时才裁剪)
  592. /// </summary>
  593. /// <param name="image"></param>
  594. /// <param name="tolerance">颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间)</param>
  595. /// <param name="toleranceMode">容差模式</param>
  596. /// <param name="maxLayers">最大检测边框层数,默认3</param>
  597. /// <param name="cropBorderCount">最少边框数</param>
  598. /// <param name="useDownscaling">是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪</param>
  599. /// <param name="downscaleFactor">缩小采样比例(1-10),默认4</param>
  600. /// <returns>是否执行了裁剪操作</returns>
  601. public static bool RemoveBorders(this Image<Rgba32> image, int tolerance, ToleranceMode toleranceMode, int maxLayers = 3, int cropBorderCount = 2, bool useDownscaling = false, int downscaleFactor = 4)
  602. {
  603. var remover = new ImageBorderRemover(toleranceMode, cropBorderCount);
  604. return remover.RemoveBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor);
  605. }
  606. }