1
1

ImageBorderRemover.cs 26 KB

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