mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2025-12-31 01:20:00 -06:00
FF-460 - added imagemagick to image nodes
This commit is contained in:
Binary file not shown.
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user