using Masuit.Tools.Systems; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Color = System.Drawing.Color; // ReSharper disable AccessToDisposedClosure namespace Masuit.Tools.Media; /// /// 图像边框移除器 /// public class ImageBorderRemover { /// /// 容差模式 /// private ToleranceMode ToleranceMode { get; } private int CroppedBorderCount { get; } /// /// /// /// 容差模式 /// 达到边框个数则裁剪 public ImageBorderRemover(ToleranceMode mode, int croppedBorderCount = 2) { ToleranceMode = mode; CroppedBorderCount = croppedBorderCount; } /// /// 检测图片边框信息(支持多色边框) /// /// 图片路径 /// 颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间) /// 最大检测边框层数,默认3 /// 是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪 /// 缩小采样比例(1-10),默认4 /// 边框检测结果 public BorderDetectionResult DetectBorders(string imagePath, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4) { using var image = Image.Load(imagePath); return DetectBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor); } /// /// 检测图片边框信息(从已加载的图像) /// /// 已加载的图像 /// 颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间) /// 最大检测边框层数,默认3 /// 是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪 /// 缩小采样比例(1-10),默认4 /// 边框检测结果 public BorderDetectionResult DetectBorders(Image image, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4) { var result = new BorderDetectionResult(CroppedBorderCount) { ImageWidth = image.Width, ImageHeight = image.Height, BorderColors = new List(), BorderLayers = 0 }; byte toleranceValue = (byte)(tolerance * 2.55); // 使用多层边框检测算法 using var clone = image.Clone(c => c.Grayscale()); var (top, bottom, left, right, layers, colors) = FindContentBordersWithLayers(clone, toleranceValue, maxLayers, useDownscaling, downscaleFactor); // 设置内容边界 result.ContentTop = top; result.ContentBottom = bottom; result.ContentLeft = left; result.ContentRight = right; result.BorderLayers = layers; result.BorderColors = colors; return result; } /// /// 自动移除图片的多层边框 /// /// 输入图片路径 /// 颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间) /// 最大检测边框层数,默认3 /// 是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪 /// 缩小采样比例(1-10),默认4 /// 是否执行了裁剪操作 public void RemoveBorders(string inputPath, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4) { RemoveBorders(inputPath, inputPath, tolerance, maxLayers, useDownscaling, downscaleFactor); } /// /// 自动移除图片的多层边框 /// /// 输入图片路径 /// 输出图片路径 /// 颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间) /// 最大检测边框层数,默认3 /// 是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪 /// 缩小采样比例(1-10),默认4 /// 是否执行了裁剪操作 public void RemoveBorders(string inputPath, string outputPath, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4) { using Image image = Image.Load(inputPath); var hasCropped = RemoveBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor); // 决定是否保存 if (hasCropped) { image.Save(outputPath); } } /// /// 自动移除图片的多层边框 /// /// 输入图片路径 /// 颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间) /// 最大检测边框层数,默认3 /// 是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪 /// 缩小采样比例(1-10),默认4 /// 是否执行了裁剪操作 public PooledMemoryStream RemoveBorders(Stream input, int tolerance, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4) { var format = Image.DetectFormat(input); input.Seek(0, SeekOrigin.Begin); Image image = Image.Load(input); RemoveBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor); var stream = new PooledMemoryStream(); image.Save(stream, format); return stream; } /// /// 自动移除图片的多层边框 /// /// /// 颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间) /// 最大检测边框层数,默认3 /// 是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪 /// 缩小采样比例(1-10),默认4 /// 是否执行了裁剪操作 public bool RemoveBorders(Image image, int tolerance, int maxLayers, bool useDownscaling, int downscaleFactor) { // 保存原始尺寸用于比较 int originalWidth = image.Width; int originalHeight = image.Height; // 使用多层检测方法获取边框信息 var borderInfo = DetectBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor); bool hasCropped = false; if (borderInfo.CanBeCropped) { int newWidth = borderInfo.ContentRight - borderInfo.ContentLeft + 1; int newHeight = borderInfo.ContentBottom - borderInfo.ContentTop + 1; if (newWidth > 0 && newHeight > 0 && (newWidth != originalWidth || newHeight != originalHeight)) { image.Mutate(x => x.Crop(new Rectangle(borderInfo.ContentLeft, borderInfo.ContentTop, newWidth, newHeight))); hasCropped = true; } } return hasCropped; } /// /// 查找内容边界(支持多层边框检测) /// private (int top, int bottom, int left, int right, int layers, List colors) FindContentBordersWithLayers(Image image, byte tolerance, int maxLayers, bool useDownscaling, int downscaleFactor) { // 如果启用缩小采样且图像足够大 Image workingImage; float scale = 1f; bool isDownscaled = false; if (useDownscaling && image.Width > 500 && image.Height > 500) { // 计算缩小尺寸 int newWidth = image.Width / downscaleFactor; int newHeight = image.Height / downscaleFactor; scale = (float)image.Width / newWidth; // 创建缩小版本用于检测 workingImage = image.Clone(ctx => ctx.Resize(newWidth, newHeight)); isDownscaled = true; } else { workingImage = image; } int width = workingImage.Width; int height = workingImage.Height; int top = 0; int bottom = height - 1; int left = 0; int right = width - 1; int layers = 0; var borderColors = new List(); // 检测多层边框 for (int layer = 0; layer < maxLayers; layer++) { bool borderFound = false; // 并行检测四个方向的边框层 var results = new (int borderSize, Rgba32? color)[4]; Parallel.Invoke(() => { if (top < height / 2) { Rgba32? layerColor = null; int newTop = DetectLayerBorderTop(workingImage, top, bottom, left, right, tolerance, ref layerColor); results[0] = (newTop - top, layerColor); if (newTop > top) borderFound = true; top = newTop; } }, () => { if (bottom > height / 2) { Rgba32? layerColor = null; int newBottom = DetectLayerBorderBottom(workingImage, top, bottom, left, right, tolerance, ref layerColor); results[1] = (newBottom - bottom, layerColor); if (newBottom < bottom) borderFound = true; bottom = newBottom; } }, () => { if (left < width / 2) { Rgba32? layerColor = null; int newLeft = DetectLayerBorderLeft(workingImage, top, bottom, left, right, tolerance, ref layerColor); results[2] = (newLeft - left, layerColor); if (newLeft > left) borderFound = true; left = newLeft; } }, () => { if (right > width / 2) { Rgba32? layerColor = null; int newRight = DetectLayerBorderRight(workingImage, top, bottom, left, right, tolerance, ref layerColor); results[3] = (newRight - right, layerColor); if (newRight < right) borderFound = true; right = newRight; } }); // 收集检测到的边框颜色 foreach (var (borderSize, color) in results) { if (color.HasValue && borderSize > 0) { borderColors.Add(color.Value); } } if (borderFound) { layers++; } else { break; // 没有检测到更多边框层 } } // 如果是缩小采样版本,映射回原图坐标 if (isDownscaled) { top = (int)(top * scale); bottom = (int)(bottom * scale); left = (int)(left * scale); right = (int)(right * scale); // 确保边界在图像范围内 top = Clamp(top, 0, image.Height - 1); bottom = Clamp(bottom, top, image.Height - 1); left = Clamp(left, 0, image.Width - 1); right = Clamp(right, left, image.Width - 1); // 释放缩小图像 workingImage.Dispose(); } return (top, bottom, left, right, layers, borderColors); } private static int Clamp(int value, int min, int max) { return value < min ? min : value > max ? max : value; } /// /// 检测顶部边框层(优化版) /// private int DetectLayerBorderTop(Image image, int currentTop, int currentBottom, int currentLeft, int currentRight, byte tolerance, ref Rgba32? borderColor) { int newTop = currentTop; Rgba32? detectedColor = null; // 使用采样检测代替全行扫描 int sampleCount = Math.Min(50, currentRight - currentLeft + 1); int stepX = Math.Max(1, (currentRight - currentLeft) / sampleCount); // 从当前顶部开始向下扫描 for (int y = currentTop; y <= currentBottom; y++) { Rgba32? rowColor = null; bool isUniform = true; // 采样检查行是否统一颜色 for (int x = currentLeft; x <= currentRight; x += stepX) { if (!rowColor.HasValue) { rowColor = image[x, y]; continue; } if (!IsSimilarColor(image[x, y], rowColor.Value, tolerance)) { isUniform = false; break; } } // 如果是统一颜色行 if (isUniform && rowColor.HasValue) { // 第一行总是被认为是边框 if (y == currentTop) { detectedColor = rowColor; newTop = y + 1; continue; } // 后续行必须与第一行颜色相似 if (detectedColor.HasValue && IsSimilarColor(rowColor.Value, detectedColor.Value, tolerance)) { newTop = y + 1; } else { break; } } else { break; } } if (newTop > currentTop) { borderColor = detectedColor; return newTop; } return currentTop; } /// /// 检测底部边框层(优化版) /// private int DetectLayerBorderBottom(Image image, int currentTop, int currentBottom, int currentLeft, int currentRight, byte tolerance, ref Rgba32? borderColor) { int newBottom = currentBottom; Rgba32? detectedColor = null; // 使用采样检测代替全行扫描 int sampleCount = Math.Min(50, currentRight - currentLeft + 1); int stepX = Math.Max(1, (currentRight - currentLeft) / sampleCount); // 从当前底部开始向上扫描 for (int y = currentBottom; y >= currentTop; y--) { Rgba32? rowColor = null; bool isUniform = true; // 采样检查行是否统一颜色 for (int x = currentLeft; x <= currentRight; x += stepX) { if (!rowColor.HasValue) { rowColor = image[x, y]; continue; } if (!IsSimilarColor(image[x, y], rowColor.Value, tolerance)) { isUniform = false; break; } } if (isUniform && rowColor.HasValue) { if (y == currentBottom) { detectedColor = rowColor; newBottom = y - 1; continue; } if (detectedColor.HasValue && IsSimilarColor(rowColor.Value, detectedColor.Value, tolerance)) { newBottom = y - 1; } else { break; } } else { break; } } if (newBottom < currentBottom) { borderColor = detectedColor; return newBottom; } return currentBottom; } /// /// 检测左侧边框层(优化版) /// private int DetectLayerBorderLeft(Image image, int currentTop, int currentBottom, int currentLeft, int currentRight, byte tolerance, ref Rgba32? borderColor) { int newLeft = currentLeft; Rgba32? detectedColor = null; // 使用采样检测代替全列扫描 int sampleCount = Math.Min(50, currentBottom - currentTop + 1); int stepY = Math.Max(1, (currentBottom - currentTop) / sampleCount); // 从当前左侧开始向右扫描 for (int x = currentLeft; x <= currentRight; x++) { Rgba32? colColor = null; bool isUniform = true; // 采样检查列是否统一颜色 for (int y = currentTop; y <= currentBottom; y += stepY) { if (!colColor.HasValue) { colColor = image[x, y]; continue; } if (!IsSimilarColor(image[x, y], colColor.Value, tolerance)) { isUniform = false; break; } } if (isUniform && colColor.HasValue) { if (x == currentLeft) { detectedColor = colColor; newLeft = x + 1; continue; } if (detectedColor.HasValue && IsSimilarColor(colColor.Value, detectedColor.Value, tolerance)) { newLeft = x + 1; } else { break; } } else { break; } } if (newLeft > currentLeft) { borderColor = detectedColor; return newLeft; } return currentLeft; } /// /// 检测右侧边框层(优化版) /// private int DetectLayerBorderRight(Image image, int currentTop, int currentBottom, int currentLeft, int currentRight, byte tolerance, ref Rgba32? borderColor) { int newRight = currentRight; Rgba32? detectedColor = null; // 使用采样检测代替全列扫描 int sampleCount = Math.Min(50, currentBottom - currentTop + 1); int stepY = Math.Max(1, (currentBottom - currentTop) / sampleCount); // 从当前右侧开始向左扫描 for (int x = currentRight; x >= currentLeft; x--) { Rgba32? colColor = null; bool isUniform = true; // 采样检查列是否统一颜色 for (int y = currentTop; y <= currentBottom; y += stepY) { if (!colColor.HasValue) { colColor = image[x, y]; continue; } if (!IsSimilarColor(image[x, y], colColor.Value, tolerance)) { isUniform = false; break; } } if (isUniform && colColor.HasValue) { if (x == currentRight) { detectedColor = colColor; newRight = x - 1; continue; } if (detectedColor.HasValue && IsSimilarColor(colColor.Value, detectedColor.Value, tolerance)) { newRight = x - 1; } else { break; } } else { break; } } if (newRight < currentRight) { borderColor = detectedColor; return newRight; } return currentRight; } /// /// 颜色相似度比较(SIMD优化) /// private bool IsSimilarColor(Rgba32 color1, Rgba32 color2, byte tolerance) { switch (ToleranceMode) { case ToleranceMode.Channel: return CompareColors(color1, color2, tolerance, true); case ToleranceMode.DeltaE2000: return Color.FromArgb(color1.A, color1.R, color1.G, color1.B).CIE2000(Color.FromArgb(color2.A, color2.R, color2.G, color2.B)) <= tolerance; case ToleranceMode.DeltaE1976: return Color.FromArgb(color1.A, color1.R, color1.G, color1.B).CIE1976(Color.FromArgb(color2.A, color2.R, color2.G, color2.B)) <= tolerance; case ToleranceMode.DeltaE1994: return Color.FromArgb(color1.A, color1.R, color1.G, color1.B).CIE1994(Color.FromArgb(color2.A, color2.R, color2.G, color2.B)) <= tolerance; case ToleranceMode.DeltaECMC: return Color.FromArgb(color1.A, color1.R, color1.G, color1.B).CMC(Color.FromArgb(color2.A, color2.R, color2.G, color2.B)) <= tolerance; case ToleranceMode.EuclideanDistance: return CompareWithEuclideanDistance(color1, color2, tolerance, true); default: throw new ArgumentOutOfRangeException(); } } /// /// 比较两个颜色是否在容差范围内相等 /// /// 第一个颜色 /// 第二个颜色 /// 容差值 (0-255) /// 是否比较Alpha通道 /// 是否匹配 private static bool CompareColors(Rgba32 color1, Rgba32 color2, int tolerance = 10, bool compareAlpha = false) { // 检查R通道 if (Math.Abs(color1.R - color2.R) > tolerance) return false; // 检查G通道 if (Math.Abs(color1.G - color2.G) > tolerance) return false; // 检查B通道 if (Math.Abs(color1.B - color2.B) > tolerance) return false; // 可选检查Alpha通道 if (compareAlpha && Math.Abs(color1.A - color2.A) > tolerance) return false; return true; } /// /// 使用欧几里得距离比较颜色 /// /// 第一个颜色 /// 第二个颜色 /// 最大允许距离 (0-442之间) /// 是否包含Alpha通道 /// 是否匹配 private static bool CompareWithEuclideanDistance(Rgba32 color1, Rgba32 color2, double maxDistance = 20.0, bool compareAlpha = false) { // 计算各分量平方差之和 double sum = Math.Pow(color1.R - color2.R, 2) + Math.Pow(color1.G - color2.G, 2) + Math.Pow(color1.B - color2.B, 2); // 可选添加Alpha通道 if (compareAlpha) { sum += Math.Pow(color1.A - color2.A, 2); } // 计算欧几里得距离 double distance = Math.Sqrt(sum); return distance <= maxDistance; } } public static class ImageBorderRemoverExt { /// /// 检测图片边框信息(从已加载的图像) /// /// 已加载的图像 /// 颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间) /// 容差模式 /// 最大检测边框层数,默认3 /// 是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪 /// 缩小采样比例(1-10),默认4 /// 边框检测结果 public static BorderDetectionResult DetectBorders(this Image image, int tolerance, ToleranceMode toleranceMode, int maxLayers = 3, bool useDownscaling = false, int downscaleFactor = 4) { var remover = new ImageBorderRemover(toleranceMode); return remover.DetectBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor); } /// /// 自动移除图片的多层边框(仅当至少有两边存在边框时才裁剪) /// /// /// 颜色容差(0-100),通道模式建议10,ΔE模式建议1-10,欧几里德模式建议(0-442之间) /// 容差模式 /// 最大检测边框层数,默认3 /// 最少边框数 /// 是否使用缩小采样优化性能,默认false,开启可能会导致图片过多裁剪 /// 缩小采样比例(1-10),默认4 /// 是否执行了裁剪操作 public static bool RemoveBorders(this Image image, int tolerance, ToleranceMode toleranceMode, int maxLayers = 3, int cropBorderCount = 2, bool useDownscaling = false, int downscaleFactor = 4) { var remover = new ImageBorderRemover(toleranceMode, cropBorderCount); return remover.RemoveBorders(image, tolerance, maxLayers, useDownscaling, downscaleFactor); } }