FF-460 - added imagemagick to image nodes

This commit is contained in:
reven
2023-04-27 09:14:45 +12:00
parent 664774c4f7
commit 2571ffd2ea
10 changed files with 236 additions and 51 deletions

Binary file not shown.

View File

@@ -2,6 +2,7 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.ComponentModel;
using ImageMagick;
namespace FileFlows.ImageNodes.Images;
@@ -18,8 +19,28 @@ public class AutoCropImage : ImageNode
[DefaultValue(50)]
public int Threshold { get; set; }
public override int Execute(NodeParameters args)
=> ExecuteImageMagick(args);
private int ExecuteImageMagick(NodeParameters args)
{
using MagickImage image = new MagickImage(args.WorkingFile);
(int originalWidth, int originalHeight) = (image.Width, image.Height);
// image magick threshold is reversed, 100 means dont trim much, 1 means trim a lot
image.Trim(new Percentage(100 - Threshold));
if (image.Width == originalWidth && image.Height == originalHeight)
return 2;
var formatOpts = GetFormat(args);
SaveImage(args, image, formatOpts.file, updateWorkingFile:true);
args.Logger?.ILog($"Image cropped from '{originalWidth}x{originalHeight}' to '{image.Width}x{image.Height}'");
return 1;
}
private int ExecuteImageSharp(NodeParameters args)
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
int originalWidth = image.Width;

View File

@@ -1,4 +1,5 @@
using SixLabors.ImageSharp.Formats;
using ImageMagick;
using SixLabors.ImageSharp.Formats;
namespace FileFlows.ImageNodes.Images;
@@ -6,61 +7,56 @@ public abstract class ImageBaseNode:Node
{
private const string IMAGE_INFO = "ImageInfo";
protected IImageFormat CurrentFormat { get; private set; }
protected string CurrentFormat { get; private set; }
protected int CurrentWidth{ get; private set; }
protected int CurrentHeight { get; private set; }
public override bool PreExecute(NodeParameters args)
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
CurrentHeight = image.Height;
CurrentWidth = image.Width;
CurrentFormat = format;
if (args.WorkingFile.ToLowerInvariant().EndsWith(".heic"))
{
using var image = new MagickImage(args.WorkingFile);
CurrentHeight = image.Height;
CurrentWidth = image.Width;
CurrentFormat = "HEIC";
}
else
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
CurrentHeight = image.Height;
CurrentWidth = image.Width;
CurrentFormat = format.Name;
}
var metadata = new Dictionary<string, object>();
metadata.Add("Format", CurrentFormat.Name);
metadata.Add("Format", CurrentFormat);
metadata.Add("Width", CurrentWidth);
metadata.Add("Height", CurrentHeight);
args.SetMetadata(metadata);
return true;
}
protected void UpdateImageInfo(NodeParameters args, Dictionary<string, object> variables = null)
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
var imageInfo = new ImageInfo
string extension = new FileInfo(args.WorkingFile).Extension[1..].ToLowerInvariant();
if (extension == "heic")
{
Width = image.Width,
Height = image.Height,
Format = format.Name
};
var metadata = new Dictionary<string, object>();
metadata.Add("Format", imageInfo.Format);
metadata.Add("Width", imageInfo.Width);
metadata.Add("Height", imageInfo.Height);
args.SetMetadata(metadata);
variables ??= new Dictionary<string, object>();
if (args.Parameters.ContainsKey(IMAGE_INFO))
args.Parameters[IMAGE_INFO] = imageInfo;
using var image = new MagickImage(args.WorkingFile);
UpdateImageInfo(args, image.Width, image.Height, "HEIC", variables);
}
else
args.Parameters.Add(IMAGE_INFO, imageInfo);
variables.AddOrUpdate("img.Width", imageInfo.Width);
variables.AddOrUpdate("img.Height", imageInfo.Height);
variables.AddOrUpdate("img.Format", imageInfo.Format);
variables.AddOrUpdate("img.IsPortrait", imageInfo.IsPortrait);
variables.AddOrUpdate("img.IsLandscape", imageInfo.IsLandscape);
args.UpdateVariables(variables);
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
UpdateImageInfo(args, image.Width, image.Height, format.Name, variables);
}
}
protected void UpdateImageInfo(NodeParameters args, Image image, IImageFormat format, Dictionary<string, object> variables = null)
protected void UpdateImageInfo(NodeParameters args, int width, int height, string format, Dictionary<string, object> variables = null)
{
var imageInfo = new ImageInfo
{
Width = image.Width,
Height = image.Height,
Format = format.Name
Width = width,
Height = height,
Format = format
};
variables ??= new Dictionary<string, object>();
@@ -84,7 +80,6 @@ public abstract class ImageBaseNode:Node
args.UpdateVariables(variables);
}
internal ImageInfo? GetImageInfo(NodeParameters args)
{
if (args.Parameters.ContainsKey(IMAGE_INFO) == false)
@@ -100,4 +95,26 @@ public abstract class ImageBaseNode:Node
}
return result;
}
/// <summary>
/// Converts an image to a format we can use, if needed
/// </summary>
/// <param name="args">the node parameters</param>
/// <returns>the filename fo the image to use</returns>
protected string ConvertImageIfNeeded(NodeParameters args)
{
string extension = new FileInfo(args.WorkingFile).Extension[1..].ToLowerInvariant();
if (extension == "heic")
{
// special case have to use imagemagick
using var image = new MagickImage(args.WorkingFile);
image.Format = MagickFormat.Png;
var newFile = Path.Combine(args.TempPath, Guid.NewGuid().ToString() + ".png");
image.Write(newFile);
return newFile;
}
return args.WorkingFile;
}
}

View File

@@ -29,8 +29,8 @@ public class ImageFile : ImageBaseNode
try
{
UpdateImageInfo(args, this.Variables);
if(string.IsNullOrEmpty(base.CurrentFormat?.Name) == false)
args.RecordStatistic("IMAGE_FORMAT", base.CurrentFormat.Name);
if(string.IsNullOrEmpty(base.CurrentFormat) == false)
args.RecordStatistic("IMAGE_FORMAT", base.CurrentFormat);
return 1;
}

View File

@@ -17,7 +17,8 @@ public class ImageFlip: ImageNode
public override int Execute(NodeParameters args)
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
var input = ConvertImageIfNeeded(args);
using var image = Image.Load(input, out IImageFormat format);
image.Mutate(c => c.Flip(Vertical ? FlipMode.Vertical : FlipMode.Horizontal));
var formatOpts = GetFormat(args);
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);

View File

@@ -1,4 +1,5 @@
using SixLabors.ImageSharp.Formats;
using ImageMagick;
using SixLabors.ImageSharp.Formats;
namespace FileFlows.ImageNodes.Images;
@@ -15,14 +16,26 @@ public class ImageFormat: ImageNode
{
var formatOpts = GetFormat(args);
if(formatOpts.format?.Name == CurrentFormat.Name)
if(formatOpts.format?.Name == CurrentFormat)
{
args.Logger?.ILog("File already in format: " + formatOpts.format.Name);
return 2;
}
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);
return 1;
string extension = new FileInfo(args.WorkingFile).Extension[1..].ToLowerInvariant();
if (extension == "heic")
{
// special case have to use imagemagick
using var image = new MagickImage(args.WorkingFile);
SaveImage(args, image, formatOpts.file);
return 1;
}
else
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);
return 1;
}
}
}

View File

@@ -1,3 +1,6 @@
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using ImageMagick;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
@@ -78,8 +81,14 @@ public abstract class ImageNode : ImageBaseNode
newFile = newFile + ".webp";
format = WebpFormat.Instance;
break;
case "HEIC":
// cant save to this format, save to PNG
newFile = newFile + ".png";
format = PngFormat.Instance;
break;
default:
newFile = newFile + "." + args.WorkingFile.Substring(args.WorkingFile.LastIndexOf(".") + 1);
newFile = Regex.Replace(newFile, @"\.heic$", ".png", RegexOptions.IgnoreCase);
break;
}
@@ -93,7 +102,54 @@ public abstract class ImageNode : ImageBaseNode
if (updateWorkingFile)
{
args.SetWorkingFile(file);
UpdateImageInfo(args, img, format, Variables);
UpdateImageInfo(args, img.Height, img.Height, format.Name, Variables);
}
}
protected void SaveImage(NodeParameters args, ImageMagick.MagickImage img, string file, bool updateWorkingFile = true)
{
using Stream outStream = new FileStream(file, FileMode.Create);
string origExtension = new FileInfo(args.WorkingFile).Extension[1..].ToLowerInvariant();
string newExtension = new FileInfo(file).Extension[1..].ToLowerInvariant();
if (origExtension != newExtension)
{
switch (newExtension)
{
case "jpg":
case "jpeg":
img.Format = MagickFormat.Jpeg;
break;
case "png":
img.Format = MagickFormat.Png;
break;
case "gif":
img.Format = MagickFormat.Gif;
break;
case "bmp":
img.Format = MagickFormat.Bmp;
break;
case "tga":
img.Format = MagickFormat.Tga;
break;
case "webp":
img.Format = MagickFormat.WebP;
break;
case "webm":
img.Format = MagickFormat.WebM;
break;
default:
if (Enum.TryParse(newExtension, true, out MagickFormat format))
img.Format = format;
break;
}
}
img.Write(outStream);
if (updateWorkingFile)
{
args.SetWorkingFile(file);
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
UpdateImageInfo(args, img.Width, img.Height, format.Name, Variables);
}
}
}

View File

@@ -55,7 +55,8 @@ public class ImageResizer: ImageNode
public override int Execute(NodeParameters args)
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
string inputFile = ConvertImageIfNeeded(args);
using var image = Image.Load(inputFile, out IImageFormat format);
SixLabors.ImageSharp.Processing.ResizeMode rzMode;
switch (Mode)
{

View File

@@ -35,7 +35,8 @@ public class ImageRotate: ImageNode
public override int Execute(NodeParameters args)
{
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
string inputFile = ConvertImageIfNeeded(args);
using var image = Image.Load(inputFile, out IImageFormat format);
image.Mutate(c => c.Rotate(Angle));
var formatOpts = GetFormat(args);
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);

View File

@@ -11,6 +11,7 @@ public class ImageNodesTests
{
string TestImage1;
string TestImage2;
string TestImageHeic;
string TempDir;
string TestCropImage1, TestCropImage2, TestCropImage3, TestCropImage4, TestCropImageNoCrop;
@@ -30,6 +31,10 @@ public class ImageNodesTests
}
else
{
TestCropImage1 = "/home/john/Pictures/cropme2.jpg";
TestCropImage2 = "/home/john/Pictures/cropme.jpg";
TestCropImage3 = "/home/john/Pictures/crop.heic";
TestImageHeic = "/home/john/Pictures/crop.heic";
TestImage1 = "/home/john/Pictures/fileflows.png";
TestImage2 = "/home/john/Pictures/36410427.png";
TempDir = "/home/john/src/temp/";
@@ -49,6 +54,35 @@ public class ImageNodesTests
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_ImageFormat_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty)
{
TempPath = TempDir
};
var node = new ImageFormat();
node.Format = "HEIC";
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_IsLandscape_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty)
{
TempPath = TempDir
};
var imageNode = new ImageFile();
imageNode.Execute(args);
var node = new ImageIsLandscape();
node.PreExecute(args);
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Resize()
{
@@ -64,6 +98,21 @@ public class ImageNodesTests
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Resize_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty)
{
TempPath = TempDir
};
var node = new ImageResizer();
node.Width = 1000;
node.Height = 500;
node.Mode = ResizeMode.Fill;
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Resize_Percent()
{
@@ -102,6 +151,19 @@ public class ImageNodesTests
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Flip_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty)
{
TempPath = TempDir
};
var node = new ImageFlip();
node.Vertical = false;
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Rotate()
@@ -116,10 +178,23 @@ public class ImageNodesTests
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Rotate_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty)
{
TempPath = TempDir
};
var node = new ImageRotate();
node.Angle = 270;
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_AutoCrop_01()
{
Assert.IsTrue(File.Exists(TestCropImage1));
var logger = new TestLogger();
var args = new NodeParameters(TestCropImage1, logger, false, string.Empty)
{
@@ -127,7 +202,7 @@ public class ImageNodesTests
};
var node = new AutoCropImage();
node.Threshold = 50;
node.Threshold = 30;
node.PreExecute(args);
int result = node.Execute(args);
@@ -162,7 +237,7 @@ public class ImageNodesTests
};
var node = new AutoCropImage();
node.Threshold = 50;
node.Threshold = 30;
node.PreExecute(args);
int result = node.Execute(args);