diff --git a/ImageNodes/Images/AutoCropImage.cs b/ImageNodes/Images/AutoCropImage.cs index 75dd98ee..26476a5e 100644 --- a/ImageNodes/Images/AutoCropImage.cs +++ b/ImageNodes/Images/AutoCropImage.cs @@ -1,4 +1,5 @@ using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using System.ComponentModel; @@ -27,16 +28,124 @@ public class AutoCropImage : ImageNode if (threshold < 0) threshold = 0.5f; - args.Logger?.ILog("Attempting to auto crop using threshold: " + threshold); - image.Mutate(c => c.EntropyCrop(threshold)); + var scaleFactor = originalWidth > 4000 && originalHeight > 4000 ? 10 : + originalWidth > 2000 && originalHeight > 2000 ? 6 : + 4; - if (image.Width == originalWidth && image.Height == originalHeight) + // entropycrop of Sixlabors does not give good results if the image has artifacts in it, eg from a scanned in image + // so we need to detect the whitespace ourselves. first we convert to greyscale and downscale, this should remove some artifacts for us + // when then look for the outer most non white pixels for out bounds + // then we upscale those bounds to the original dimensions and crop with those bounds + image.Mutate(c => + { + c.Grayscale().Resize(originalWidth / scaleFactor, originalHeight / scaleFactor); + }); + string temp = Path.Combine(args.TempPath, Guid.NewGuid() + ".jpg"); + image.SaveAsJpeg(temp); + var bounds = GetTrimBounds(temp); + bounds.X *= scaleFactor; + bounds.Y *= scaleFactor; + bounds.Width *= scaleFactor; + bounds.Height *= scaleFactor; + image.Dispose(); + + args.Logger?.ILog("Attempting to auto crop using threshold: " + threshold); + if (bounds.Width == originalWidth && bounds.Height == originalHeight) + return 2; + + using var image2 = Image.Load(args.WorkingFile, out format); + + + image2.Mutate(c => + { + c.Crop(bounds); + //c.EntropyCrop(threshold); + }); + + if (image2.Width == originalWidth && image2.Height == originalHeight) return 2; var formatOpts = GetFormat(args); - SaveImage(args, image, formatOpts.file, formatOpts.format ?? format); - args.Logger?.ILog($"Image cropped from '{originalWidth}x{originalHeight}' to '{image.Width}x{image.Height}'"); + SaveImage(args, image2, formatOpts.file, formatOpts.format ?? format); + args.Logger?.ILog($"Image cropped from '{originalWidth}x{originalHeight}' to '{image2.Width}x{image2.Height}'"); return 1; } + + public Rectangle GetTrimBounds(string file) + { + int threshhold = 255 - (this.Threshold / 4); + + + int topOffset = 0; + int bottomOffset = 0; + int leftOffset = 0; + int rightOffset = 0; + + + using Image image = Image.Load(file); + bool foundColor = false; + // Get left bounds to crop + for (int x = 1; x < image.Width && foundColor == false; x++) + { + for (int y = 1; y < image.Height && foundColor == false; y++) + { + var color = image[x, y]; + if (color.R < threshhold || color.G < threshhold || color.B < threshhold) + foundColor = true; + } + leftOffset += 1; + } + + + foundColor = false; + // Get top bounds to crop + for (int y = 1; y < image.Height && foundColor == false; y++) + { + for (int x = 1; x < image.Width && foundColor == false; x++) + { + var color = image[x, y]; + if (color.R < threshhold || color.G < threshhold || color.B < threshhold) + foundColor = true; + } + topOffset += 1; + } + + + foundColor = false; + // Get right bounds to crop + for (int x = image.Width - 1; x >= 1 && foundColor == false; x--) + { + for (int y = 1; y < image.Height && foundColor == false; y++) + { + var color = image[x, y]; + if (color.R < threshhold || color.G < threshhold || color.B < threshhold) + foundColor = true; + } + rightOffset += 1; + } + + + foundColor = false; + // Get bottom bounds to crop + for (int y = image.Height - 1; y >= 1 && foundColor == false; y--) + { + for (int x = 1; x < image.Width && foundColor == false; x++) + { + var color = image[x, y]; + if (color.R < threshhold || color.G < threshhold || color.B < threshhold) + foundColor = true; + } + bottomOffset += 1; + } + + + var bounds = new Rectangle( + leftOffset, + topOffset, + image.Width - leftOffset - rightOffset, + image.Height - topOffset - bottomOffset + ); + return bounds; + } } diff --git a/ImageNodes/Tests/ImageTests.cs b/ImageNodes/Tests/ImageTests.cs index 142dbf18..28eaeb67 100644 --- a/ImageNodes/Tests/ImageTests.cs +++ b/ImageNodes/Tests/ImageTests.cs @@ -12,7 +12,7 @@ public class ImageNodesTests string TestImage1; string TestImage2; string TempDir; - string TestCropImage1, TestCropImage2, TestCropImage3, TestCropImageNoCrop; + string TestCropImage1, TestCropImage2, TestCropImage3, TestCropImage4, TestCropImageNoCrop; public ImageNodesTests() { @@ -25,6 +25,7 @@ public class ImageNodesTests TestCropImage1 = @"D:\images\testimages\crop01.jpg"; TestCropImage2 = @"D:\images\testimages\crop02.jpg"; TestCropImage3 = @"D:\images\testimages\crop03.jpg"; + TestCropImage4 = @"D:\images\testimages\crop04.jpg"; TestCropImageNoCrop = @"D:\images\testimages\nocrop.jpg"; } else @@ -126,6 +127,8 @@ public class ImageNodesTests }; var node = new AutoCropImage(); + node.Threshold = 50; + node.PreExecute(args); int result = node.Execute(args); string log = logger.ToString(); @@ -159,6 +162,32 @@ public class ImageNodesTests }; var node = new AutoCropImage(); + node.Threshold = 50; + node.PreExecute(args); + int result = node.Execute(args); + + string log = logger.ToString(); + Assert.AreEqual(1, result); + } + + [TestMethod] + public void ImageNodes_Basic_AutoCrop_04() + { + var logger = new TestLogger(); + var args = new NodeParameters(TestCropImage4, logger, false, string.Empty) + { + TempPath = TempDir + }; + + //var rotate = new ImageRotate(); + //rotate.Angle = 270; + //rotate.PreExecute(args); + //rotate.Execute(args); + + + var node = new AutoCropImage(); + node.Threshold = 70; + node.PreExecute(args); int result = node.Execute(args); string log = logger.ToString(); @@ -175,6 +204,8 @@ public class ImageNodesTests }; var node = new AutoCropImage(); + node.Threshold = 50; + node.PreExecute(args); int result = node.Execute(args); string log = logger.ToString();