Files
FileFlowsPlugins/ImageNodes/Images/AutoCropImage.cs
2022-09-10 15:50:59 +12:00

152 lines
5.0 KiB
C#

using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.ComponentModel;
namespace FileFlows.ImageNodes.Images;
public class AutoCropImage : ImageNode
{
public override int Inputs => 1;
public override int Outputs => 2;
public override FlowElementType Type => FlowElementType.Process;
public override string HelpUrl => "https://docs.fileflows.com/plugins/image-nodes/auto-crop-image";
public override string Icon => "fas fa-crop";
[Slider(1)]
[Range(1, 100)]
[DefaultValue(50)]
public int Threshold { get; set; }
public override int Execute(NodeParameters args)
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
int originalWidth = image.Width;
int originalHeight= image.Height;
float threshold = Threshold / 100f;
if (threshold < 0)
threshold = 0.5f;
var scaleFactor = originalWidth > 4000 && originalHeight > 4000 ? 10 :
originalWidth > 2000 && originalHeight > 2000 ? 6 :
4;
// 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, 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<Rgba32> image = Image.Load<Rgba32>(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;
}
}