FF-1536: Redeveloped Image Plugin

This commit is contained in:
John Andrews
2024-05-10 17:53:09 +12:00
parent 5fc95f0510
commit e1bf31080d
24 changed files with 710 additions and 953 deletions

View File

@@ -1,9 +1,7 @@
global using System;
global using System.Text;
global using System.ComponentModel.DataAnnotations;
global using System.Collections.Generic;
global using FileFlows.Plugin;
global using FileFlows.Plugin.Attributes;
global using SixLabors.ImageSharp;
global using FileHelper = FileFlows.Plugin.Helpers.FileHelper;
global using static FileFlows.ImageNodes.Images.Constants;

View File

@@ -1,10 +0,0 @@
namespace FileFlows.ImageNodes;
public class ImageInfo
{
public int Width { get; set; }
public int Height { get; set; }
public string Format { get; set; } = string.Empty;
public bool IsPortrait => Width < Height;
public bool IsLandscape => Height < Width;
}

View File

@@ -25,10 +25,6 @@
<HintPath>..\FileFlows.Plugin.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.6.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<None Update="i18n\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>

View File

@@ -1,172 +1,32 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.ComponentModel;
using ImageMagick;
using System.ComponentModel;
namespace FileFlows.ImageNodes.Images;
/// <summary>
/// Flow element that crops an image of any white/black borders
/// </summary>
public class AutoCropImage : ImageNode
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 2;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Process;
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/image-nodes/auto-crop-image";
/// <inheritdoc />
public override string Icon => "fas fa-crop";
/// <summary>
/// Gets or sets the crop threshold
/// </summary>
[Slider(1)]
[Range(1, 100)]
[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)
{
var format = Image.DetectFormat(args.WorkingFile);
using var image = Image.Load(args.WorkingFile);
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 = FileHelper.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;
format = Image.DetectFormat(args.WorkingFile);
using var image2 = Image.Load(args.WorkingFile);
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;
}
/// <inheritdoc />
protected override Result<bool> PerformAction(NodeParameters args, string localFile, string destination)
=> args.ImageHelper.Trim(localFile, destination, Threshold, GetImageTypeFromFormat(), Quality);
}

View File

@@ -1,12 +0,0 @@
namespace FileFlows.ImageNodes.Images;
public enum ResizeMode
{
Fill = 1,
Contain = 2,
Cover = 3,
None = 4,
Min = 5,
Max = 6,
}

View File

@@ -1,5 +1,4 @@
using ImageMagick;
using SixLabors.ImageSharp.Formats;
using FileFlows.Plugin.Helpers;
namespace FileFlows.ImageNodes.Images;
@@ -42,118 +41,86 @@ public abstract class ImageBaseNode:Node
args.Logger?.ELog("Working file cannot be read: " + localFile.Error);
return false;
}
if (args.WorkingFile.ToLowerInvariant().EndsWith(".heic"))
var info = args.ImageHelper.GetInfo(localFile);
if(info.Failed(out string error))
{
using var image = new MagickImage(localFile);
CurrentHeight = image.Height;
CurrentWidth = image.Width;
CurrentFormat = "HEIC";
}
else
{
var format = Image.DetectFormat(localFile);
using var image = Image.Load(localFile);
CurrentHeight = image.Height;
CurrentWidth = image.Width;
CurrentFormat = format.Name;
args.FailureReason = error;
args.Logger?.ELog(error);
return false;
}
var metadata = new Dictionary<string, object>();
metadata.Add("Format", CurrentFormat);
metadata.Add("Width", CurrentWidth);
metadata.Add("Height", CurrentHeight);
if(string.IsNullOrWhiteSpace(info.Value.Format) == false)
metadata.Add("Format", info.Value.Format);
metadata.Add("Width", info.Value.Width);
metadata.Add("Height", info.Value.Height);
CurrentFormat = info.Value.Format;
CurrentHeight = info.Value.Height;
CurrentWidth = info.Value.Width;
args.SetMetadata(metadata);
UpdateImageInfo(args, info);
return true;
}
/// <summary>
/// Updates information about an image based on the provided NodeParameters and optional variables.
/// </summary>
/// <param name="args">The NodeParameters</param>
/// <param name="variables">Additional variables associated with the image (optional).</param>
protected void UpdateImageInfo(NodeParameters args, Dictionary<string, object>? variables = null)
protected void ReadWorkingFileInfo(NodeParameters args)
{
string extension = FileHelper.GetExtension(args.WorkingFile).ToLowerInvariant().TrimStart('.');
if (extension == "heic")
var localFile = args.FileService.GetLocalPath(args.WorkingFile);
if (localFile.Failed(out string error))
{
using var image = new MagickImage(args.WorkingFile);
UpdateImageInfo(args, image.Width, image.Height, "HEIC", variables);
args.Logger?.ELog("Working file cannot be read: " + error);
return;
}
else
var info = args.ImageHelper.GetInfo(localFile);
if (info.Failed(out error))
{
var format = Image.DetectFormat(args.WorkingFile);
using var image = Image.Load(args.WorkingFile);
DateTime? dateTaken = null;
if (image.Metadata.ExifProfile != null)
{
args.Logger?.ILog("EXIF Profile found");
if(image.Metadata.ExifProfile.TryGetValue(SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag.DateTimeOriginal, out var dateTimeOriginalString) == false
|| string.IsNullOrWhiteSpace(dateTimeOriginalString?.Value))
{
args.Logger?.ILog("No DateTimeOriginal found");
}
else
{
if (TryParseDateTime(dateTimeOriginalString.Value, out DateTime? dateTimeOriginal))
{
dateTaken = dateTimeOriginal;
args.Logger?.ILog("DateTimeOriginal: " + dateTimeOriginal);
}
else
{
args.Logger?.ILog("Invalid date format for DateTimeOriginal: " + dateTimeOriginalString.Value);
}
}
}
else
{
args.Logger?.ILog("No EXIF Profile found");
}
UpdateImageInfo(args, image.Width, image.Height, format.Name, variables: variables, dateTaken: dateTaken);
args.Logger?.ELog(error);
return;
}
UpdateImageInfo(args, info);
}
/// <summary>
/// Updates information about an image based on the provided NodeParameters, width, height, format, variables, and dateTaken.
/// </summary>
/// <param name="args">The NodeParameters</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="format">The format of the image.</param>
/// <param name="variables">Additional variables associated with the image (optional).</param>
/// <param name="dateTaken">The date when the image was taken (optional).</param>
protected void UpdateImageInfo(NodeParameters args, int width, int height, string format, Dictionary<string, object>? variables = null, DateTime? dateTaken = null)
/// <param name="imageInfo">The image info</param>
protected void UpdateImageInfo(NodeParameters args, ImageInfo imageInfo)
{
var imageInfo = new ImageInfo
{
Width = width,
Height = height,
Format = format
};
variables ??= new Dictionary<string, object>();
args.Parameters[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);
if (dateTaken != null)
{
variables.AddOrUpdate("img.DateTaken", dateTaken.Value);
}
var metadata = new Dictionary<string, object>();
metadata.Add("Format", imageInfo.Format);
args.Variables["img.Width"] = imageInfo.Width;
args.Variables["img.Height"] = imageInfo.Height;
metadata.Add("Width", imageInfo.Width);
metadata.Add("Height", imageInfo.Height);
args.SetMetadata(metadata);
if (string.IsNullOrWhiteSpace(imageInfo.Format))
{
args.Variables.Remove("img.Format");
}
else
{
args.Variables["img.Format"] = imageInfo.Format;
metadata["img.Format"] = imageInfo.Format;
}
args.UpdateVariables(variables);
args.Variables["img.IsPortrait"] = imageInfo.IsPortrait;
args.Variables["img.IsLandscape"] = imageInfo.IsLandscape;
if (imageInfo.DateTaken != null)
args.Variables["img.DateTaken"] = imageInfo.DateTaken.Value;
else
args.Variables.Remove("img.DateTaken");
args.SetMetadata(metadata);
}
/// <summary>
@@ -165,12 +132,12 @@ public abstract class ImageBaseNode:Node
/// </returns>
internal ImageInfo? GetImageInfo(NodeParameters args)
{
if (args.Parameters.ContainsKey(IMAGE_INFO) == false)
if (args.Parameters.TryGetValue(IMAGE_INFO, out var parameter) == false)
{
args.Logger?.WLog("No image information loaded, use a 'Image File' flow element first");
return null;
}
var result = args.Parameters[IMAGE_INFO] as ImageInfo;
var result = parameter as ImageInfo;
if (result == null)
{
args.Logger?.WLog("ImageInfo not found for file");
@@ -179,63 +146,5 @@ 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 = FileHelper.GetExtension(args.WorkingFile).ToLowerInvariant().TrimStart('.');
if (extension == "heic")
{
// special case have to use imagemagick
using var image = new MagickImage(args.WorkingFile);
image.Format = MagickFormat.Png;
var newFile = FileHelper.Combine(args.TempPath, Guid.NewGuid() + ".png");
image.Write(newFile);
return newFile;
}
return args.WorkingFile;
}
/// <summary>
/// Tries to parse a DateTime from a string, attempting different formats.
/// </summary>
/// <param name="dateTimeString">The string representation of the DateTime.</param>
/// <param name="dateTime">When this method returns, contains the parsed DateTime if successful; otherwise, null.</param>
/// <returns>
/// True if the parsing was successful; otherwise, false.
/// </returns>
static bool TryParseDateTime(string dateTimeString, out DateTime? dateTime)
{
DateTime parsedDateTime;
// Try parsing using DateTime.TryParse
if (DateTime.TryParse(dateTimeString, out parsedDateTime))
{
dateTime = parsedDateTime;
return true;
}
// Define an array of possible date formats for additional attempts
string[] dateFormats = { "yyyy:MM:dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss" /* Add more formats if needed */ };
// Attempt to parse using different formats
foreach (var format in dateFormats)
{
if (DateTime.TryParseExact(dateTimeString, format, null, System.Globalization.DateTimeStyles.None, out parsedDateTime))
{
dateTime = parsedDateTime;
return true;
}
}
// Set dateTime to null if parsing fails with all formats
dateTime = null;
return false;
}
}

View File

@@ -0,0 +1,38 @@
namespace FileFlows.ImageNodes.Images;
/// <summary>
/// Converts an image to another format
/// </summary>
public class ImageConvert: ImageNode
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 2;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Process;
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/image-nodes/image-convert";
/// <inheritdoc />
public override string Icon => "fas fa-file-image";
/// <inheritdoc />
protected override Result<bool> PerformAction(NodeParameters args, string localFile, string destination)
{
var format = GetImageTypeFromFormat();
if (format == null)
{
args.Logger?.ILog("Format not set, nothing to do.");
return false;
}
if (args.WorkingFile.ToLowerInvariant().EndsWith("." + Format.ToLowerInvariant()))
{
args.Logger?.ILog("File already in format: " + Format);
return false;
}
return args.ImageHelper.ConvertImage(localFile,
destination,
format.Value, Quality);
}
}

View File

@@ -37,7 +37,6 @@ public class ImageFile : ImageBaseNode
if (args.FileService.FileLastWriteTimeUtc(args.WorkingFile).Success(out DateTime writeTime))
args.Variables["ORIGINAL_LAST_WRITE_UTC"] = writeTime;
UpdateImageInfo(args, this.Variables);
if(string.IsNullOrEmpty(base.CurrentFormat) == false)
args.RecordStatisticRunningTotals("IMAGE_FORMAT", base.CurrentFormat);

View File

@@ -1,29 +1,34 @@
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Processing;
namespace FileFlows.ImageNodes.Images;
namespace FileFlows.ImageNodes.Images;
public class ImageFlip: ImageNode
/// <summary>
/// Flow element to flip an image
/// </summary>
public class ImageFlip : ImageNode
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 1;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Process;
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/image-nodes/image-flip";
/// <inheritdoc />
public override string Icon => "fas fa-sync-alt";
/// <summary>
/// Gets or sets if the image should be flipped vertically, otherwise its flipped horizontally
/// </summary>
[Boolean(2)]
public bool Vertical { get; set; }
public override int Execute(NodeParameters args)
{
var input = ConvertImageIfNeeded(args);
var format = Image.DetectFormat(input);
using var image = Image.Load(input);
image.Mutate(c => c.Flip(Vertical ? FlipMode.Vertical : FlipMode.Horizontal));
var formatOpts = GetFormat(args);
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);
return 1;
}
/// <inheritdoc />
protected override Result<bool> PerformAction(NodeParameters args, string localFile, string destination)
=> Vertical
? args.ImageHelper.FlipVertically(localFile, destination, GetImageTypeFromFormat(), Quality)
: args.ImageHelper.FlipHorizontally(localFile, destination, GetImageTypeFromFormat(), Quality);
}

View File

@@ -1,42 +0,0 @@
using ImageMagick;
using SixLabors.ImageSharp.Formats;
namespace FileFlows.ImageNodes.Images;
public class ImageFormat: ImageNode
{
public override int Inputs => 1;
public override int Outputs => 2;
public override FlowElementType Type => FlowElementType.Process;
public override string HelpUrl => "https://fileflows.com/docs/plugins/image-nodes/image-format";
public override string Icon => "fas fa-file-image";
public override int Execute(NodeParameters args)
{
var formatOpts = GetFormat(args);
if(formatOpts.format?.Name == CurrentFormat)
{
args.Logger?.ILog("File already in format: " + (formatOpts.format?.Name ?? string.Empty));
return 2;
}
string extension = FileHelper.GetExtension(args.WorkingFile).ToLowerInvariant().TrimStart('.');
if (extension == "heic")
{
// special case have to use imagemagick
using var image = new MagickImage(args.WorkingFile);
SaveImage(args, image, formatOpts.file);
return 1;
}
else
{
var format = Image.DetectFormat(args.WorkingFile);
using var image = Image.Load(args.WorkingFile);
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);
return 1;
}
}
}

View File

@@ -1,15 +1,23 @@
namespace FileFlows.ImageNodes.Images;
/// <summary>
/// Checks if an image is landascape
/// </summary>
public class ImageIsLandscape: ImageBaseNode
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 2;
public override FlowElementType Type => FlowElementType.Logic;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Logic;
/// <inheritdoc />
public override string Icon => "fas fa-image";
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/image-nodes/image-is-landscape";
/// <inheritdoc />
public override int Execute(NodeParameters args)
{
var img = GetImageInfo(args);

View File

@@ -1,13 +1,21 @@
namespace FileFlows.ImageNodes.Images;
/// <summary>
/// Checks if an image is portrait
/// </summary>
public class ImageIsPortrait : ImageBaseNode
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 2;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Logic;
/// <inheritdoc />
public override string Icon => "fas fa-portrait";
/// <inheritdoc />
public override int Execute(NodeParameters args)
{
var img = GetImageInfo(args);

View File

@@ -1,23 +1,24 @@
using System.Text.RegularExpressions;
using ImageMagick;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
using System.ComponentModel;
using FileFlows.Plugin.Helpers;
namespace FileFlows.ImageNodes.Images;
/// <summary>
/// Base image flow element
/// </summary>
public abstract class ImageNode : ImageBaseNode
{
[Select(nameof(FormatOptions), 1)]
/// <summary>
/// Gets or sets the format to save the image in
/// </summary>
[Select(nameof(FormatOptions), 51)]
public string Format { get; set; } = string.Empty;
private static List<ListOption>? _FormatOptions;
/// <summary>
/// Gets the image format options
/// </summary>
public static List<ListOption> FormatOptions
{
get
@@ -27,152 +28,103 @@ public abstract class ImageNode : ImageBaseNode
_FormatOptions = new List<ListOption>
{
new () { Value = "", Label = "Same as source"},
new () { Value = IMAGE_FORMAT_BMP, Label = "Bitmap"},
new () { Value = IMAGE_FORMAT_GIF, Label = "GIF"},
new () { Value = IMAGE_FORMAT_JPEG, Label = "JPEG"},
new () { Value = IMAGE_FORMAT_PBM, Label = "PBM"},
new () { Value = IMAGE_FORMAT_PNG, Label = "PNG"},
new () { Value = IMAGE_FORMAT_TIFF, Label = "TIFF"},
new () { Value = "###GROUP###", Label = "Lossless Formats" },
new () { Value = IMAGE_FORMAT_PNG, Label = "PNG" },
new () { Value = IMAGE_FORMAT_BMP, Label = "Bitmap" },
new () { Value = IMAGE_FORMAT_TIFF, Label = "TIFF" },
new () { Value = IMAGE_FORMAT_TGA, Label = "TGA" },
new () { Value = IMAGE_FORMAT_WEBP, Label = "WebP"},
new () { Value = IMAGE_FORMAT_WEBP, Label = "WebP" },
new () { Value = "###GROUP###", Label = "Lossy Formats" },
new () { Value = IMAGE_FORMAT_JPEG, Label = "JPEG" },
new () { Value = IMAGE_FORMAT_GIF, Label = "GIF" },
new () { Value = IMAGE_FORMAT_PBM, Label = "PBM" },
};
}
return _FormatOptions;
}
}
/// <summary>
/// Gets or sets the quality to save the image in
/// </summary>
[Slider(52)]
[Range(1, 100)]
[DefaultValue(100)]
[ConditionEquals(nameof(Format), $"/^({IMAGE_FORMAT_WEBP}|{IMAGE_FORMAT_JPEG})$/")]
public int Quality { get; set; }
protected (IImageFormat? format, string file) GetFormat(NodeParameters args)
/// <summary>
/// Gets the image type from the image format
/// </summary>
/// <returns>the image type, or null to keep original</returns>
protected ImageType? GetImageTypeFromFormat()
{
IImageFormat? format = null;
var newFile = FileHelper.Combine(args.TempPath, Guid.NewGuid().ToString());
switch (this.Format)
switch (Format)
{
case IMAGE_FORMAT_BMP:
newFile = newFile + ".bmp";
format = BmpFormat.Instance;
break;
case IMAGE_FORMAT_GIF:
newFile = newFile + ".gif";
format = GifFormat.Instance;
break;
case IMAGE_FORMAT_JPEG:
newFile = newFile + ".jpg";
format = JpegFormat.Instance;
break;
case IMAGE_FORMAT_PBM:
newFile = newFile + ".pbm";
format = PbmFormat.Instance;
break;
case IMAGE_FORMAT_PNG:
newFile = newFile + ".png";
format = PngFormat.Instance;
break;
case IMAGE_FORMAT_TIFF:
newFile = newFile + ".tiff";
format = TiffFormat.Instance;
break;
case IMAGE_FORMAT_TGA:
newFile = newFile + ".tga";
format = TgaFormat.Instance;
break;
case IMAGE_FORMAT_WEBP:
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;
case IMAGE_FORMAT_BMP: return ImageType.Bmp;
case IMAGE_FORMAT_GIF: return ImageType.Gif;
case IMAGE_FORMAT_TGA: return ImageType.Tga;
case IMAGE_FORMAT_PNG: return ImageType.Png;
case IMAGE_FORMAT_PBM: return ImageType.Pbm;
case IMAGE_FORMAT_JPEG: return ImageType.Jpeg;
case IMAGE_FORMAT_TIFF: return ImageType.Tiff;
case IMAGE_FORMAT_WEBP: return ImageType.Webp;
}
return (format, newFile);
return null;
}
protected void SaveImage(NodeParameters args, Image img, string file, IImageFormat format, bool updateWorkingFile = true)
/// <summary>
/// Gets the image type extension with leading period
/// </summary>
/// <param name="file">the current file, will be used if the type is not set</param>
/// <returns>the extension with leading period</returns>
protected string GetImageTypeExtension(string file)
{
string local = args.FileService.FileIsLocal(file)
? file
: FileHelper.Combine(args.TempPath, Guid.NewGuid() + FileHelper.GetExtension(file));
using var outStream = new System.IO.FileStream(local, System.IO.FileMode.Create);
img.Save(outStream, format);
if (local != file && args.FileService.FileMove(local, file).Failed(out string error))
{
args.Logger?.ELog("Failed to move saved file: " + error);
return;
}
if (updateWorkingFile)
{
args.SetWorkingFile(file);
UpdateImageInfo(args, img.Height, img.Height, format.Name, Variables);
}
var type = GetImageTypeFromFormat();
if (type == ImageType.Jpeg)
return ".jpg";
if (type == null)
return FileHelper.GetExtension(file);
return "." + (type.ToString()!.ToLowerInvariant());
}
protected void SaveImage(NodeParameters args, ImageMagick.MagickImage img, string file, bool updateWorkingFile = true)
/// <inheritdoc />
public override int Execute(NodeParameters args)
{
string local = args.FileService.FileIsLocal(file)
? file
: FileHelper.Combine(args.TempPath, Guid.NewGuid() + FileHelper.GetExtension(file));
using var outStream = new System.IO.FileStream(local, System.IO.FileMode.Create);
string origExtension = FileHelper.GetExtension(args.WorkingFile).ToLowerInvariant().TrimStart('.');
string newExtension = FileHelper.GetExtension(file).ToLowerInvariant().TrimStart('.');
if (origExtension != newExtension)
var localFile = args.FileService.GetLocalPath(args.WorkingFile);
if (localFile.Failed(out string error))
{
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;
}
args.FailureReason = "Failed to get local file: " + localFile.Error;
args.Logger?.ELog(args.FailureReason);
return -1;
}
img.Write(outStream);
var destination = FileHelper.Combine(args.TempPath, Guid.NewGuid() + GetImageTypeExtension(localFile));
var result = PerformAction(args, localFile, destination);
if (updateWorkingFile)
if (result.Failed(out error))
{
args.SetWorkingFile(file);
var format = Image.DetectFormat(local);
using var image = Image.Load(local);
UpdateImageInfo(args, img.Width, img.Height, format.Name, Variables);
}
if (local != file && args.FileService.FileMove(local, file).Failed(out string error))
{
args.Logger?.ELog("Failed to move saved file: " + error);
args.FailureReason = error;
args.Logger?.ELog(args.FailureReason);
return -1;
}
if (result == false)
return 2;
args.SetWorkingFile(destination);
ReadWorkingFileInfo(args);
return 1;
}
/// <summary>
/// Performs the image action
/// </summary>
/// <param name="args">the node parameters</param>
/// <param name="localFile">the local file</param>
/// <param name="destination">the destination file to create</param>
/// <returns>true if successful (output 1), false if not (output 2), failure result if failed</returns>
protected abstract Result<bool> PerformAction(NodeParameters args, string localFile, string destination);
}

View File

@@ -1,27 +1,28 @@
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Processing;
using FileFlows.Plugin.Helpers;
using FileFlows.Plugin.Types;
namespace FileFlows.ImageNodes.Images;
/// <summary>
/// Flow element that resizes an image
/// </summary>
public class ImageResizer: ImageNode
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 1;
public override FlowElementType Type => FlowElementType.Process;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Process;
/// <inheritdoc />
public override string Icon => "fas fa-expand";
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/image-nodes/image-resizer";
[Select(nameof(ResizeModes), 2)]
/// <summary>
/// Gets or sets the resize mode
/// </summary>
[Select(nameof(ResizeModes), 3)]
public ResizeMode Mode { get; set; }
private static List<ListOption>? _ResizeModes;
@@ -37,67 +38,36 @@ public class ImageResizer: ImageNode
{
_ResizeModes = new List<ListOption>
{
new () { Value = ResizeMode.Fill, Label = "Fill (Stretches to fit)"},
new () { Value = ResizeMode.Contain, Label = "Contain (Preserves aspect ratio but contained in bounds)"},
new () { Value = ResizeMode.Cover, Label = "Cover (Preserves aspect ratio)"},
new () { Value = ResizeMode.Min, Label = "Min (Resizes the image until the shortest side reaches the set given dimension)"},
new () { Value = ResizeMode.Max, Label = "Max (Constrains the resized image to fit the bounds of its container maintaining the original aspect ratio)"},
new () { Value = ResizeMode.None, Label = "None (Not resized)"}
new () { Value = ResizeMode.Fill, Label = "Fill"},
new () { Value = ResizeMode.Contain, Label = "Contain"},
new () { Value = ResizeMode.Cover, Label = "Cover"},
new () { Value = ResizeMode.Min, Label = "Min"},
new () { Value = ResizeMode.Max, Label = "Max"},
new () { Value = ResizeMode.Pad, Label = "Pad"}
};
}
return _ResizeModes;
}
}
[NumberInt(3)]
[Range(1, int.MaxValue)]
public int Width { get; set; }
[NumberInt(4)]
[Range(1, int.MaxValue)]
public int Height { get; set; }
[Boolean(5)]
public bool Percent { get; set; }
public override int Execute(NodeParameters args)
/// <summary>
/// Gets or sets the new width
/// </summary>
[NumberPercent(5, "Labels.Pixels", 0, false)]
public NumberPercent? Width { get; set; }
/// <summary>
/// Gets or sets the new height
/// </summary>
[NumberPercent(6, "Labels.Pixels", 0, false)]
public NumberPercent? Height { get; set; }
/// <inheritdoc />
protected override Result<bool> PerformAction(NodeParameters args, string localFile, string destination)
{
string inputFile = ConvertImageIfNeeded(args);
var format = Image.DetectFormat(inputFile);
using var image = Image.Load(inputFile);
SixLabors.ImageSharp.Processing.ResizeMode rzMode;
switch (Mode)
{
case ResizeMode.Contain: rzMode = SixLabors.ImageSharp.Processing.ResizeMode.Pad;
break;
case ResizeMode.Cover: rzMode = SixLabors.ImageSharp.Processing.ResizeMode.Crop;
break;
case ResizeMode.Fill: rzMode = SixLabors.ImageSharp.Processing.ResizeMode.Stretch;
break;
case ResizeMode.Min: rzMode = SixLabors.ImageSharp.Processing.ResizeMode.Min;
break;
case ResizeMode.Max: rzMode = SixLabors.ImageSharp.Processing.ResizeMode.Max;
break;
default: rzMode = SixLabors.ImageSharp.Processing.ResizeMode.BoxPad;
break;
}
var formatOpts = GetFormat(args);
float w = Width;
float h = Height;
if (Percent)
{
w = (int)(image.Width * (w / 100f));
h = (int)(image.Height * (h / 100f));
}
image.Mutate(c => c.Resize(new ResizeOptions()
{
Size = new Size((int)w, (int)h),
Mode = rzMode
}));
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);
return 1;
float w = Width!.Percentage ? (int)(CurrentWidth * (Width.Value / 100f)) : Width.Value;
float h = Height!.Percentage ? (int)(CurrentHeight * (Height.Value / 100f)) : Height.Value;
return args.ImageHelper.Resize(localFile, destination, (int)w, (int)h, Mode, GetImageTypeFromFormat(), Quality);
}
}

View File

@@ -1,17 +1,24 @@
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Processing;
namespace FileFlows.ImageNodes.Images;
namespace FileFlows.ImageNodes.Images;
/// <summary>
/// Flow element that rotates an image
/// </summary>
public class ImageRotate: ImageNode
{
/// <inheritdoc />
public override int Inputs => 1;
/// <inheritdoc />
public override int Outputs => 1;
public override FlowElementType Type => FlowElementType.Process;
/// <inheritdoc />
public override FlowElementType Type => FlowElementType.Process;
/// <inheritdoc />
public override string Icon => "fas fa-undo";
/// <inheritdoc />
public override string HelpUrl => "https://fileflows.com/docs/plugins/image-nodes/image-rotate";
/// <summary>
/// Gets or sets angle to rotate the image
/// </summary>
[Select(nameof(AngleOptions), 2)]
public int Angle { get; set; }
@@ -36,15 +43,7 @@ public class ImageRotate: ImageNode
}
}
public override int Execute(NodeParameters args)
{
string inputFile = ConvertImageIfNeeded(args);
var format = Image.DetectFormat(inputFile);
using var image = Image.Load(inputFile);
image.Mutate(c => c.Rotate(Angle));
var formatOpts = GetFormat(args);
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);
return 1;
}
/// <inheritdoc />
protected override Result<bool> PerformAction(NodeParameters args, string localFile, string destination)
=> args.ImageHelper.Rotate(localFile, destination, Angle, GetImageTypeFromFormat(), Quality);
}

View File

@@ -1,317 +1,317 @@
#if(DEBUG)
using FileFlows.ImageNodes.Images;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
namespace FileFlows.ImageNodes.Tests;
[TestClass]
public class ImageNodesTests
{
string? TestImage1;
string? TestImage2;
string? TestImageHeic;
string TempDir;
string? TestCropImage1, TestCropImage2, TestCropImage3, TestCropImage4, TestCropImageNoCrop, TestExif;
public ImageNodesTests()
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
TestImage1 = @"D:\videos\pictures\image1.jpg";
TestImage2 = @"D:\videos\pictures\image2.png";
TempDir = @"D:\videos\temp";
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
{
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/circle.jpg";
TestImage2 = "/home/john/Pictures/36410427.png";
TempDir = "/home/john/temp/";
TestExif = "/home/john/Pictures/exif_test.jpg";
}
}
[TestMethod]
public void ImageNodes_Basic_ImageFormat()
{
var args = new NodeParameters(TestImage1, new TestLogger(), false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new ImageFormat();
node.Format = IMAGE_FORMAT_GIF;
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_ImageFormat_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty, null!)
{
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, null!)
{
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()
{
var logger = new TestLogger();
var args = new NodeParameters(TestImage1, logger, false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new ImageResizer();
node.Width = 1920;
node.Height = 1080;
node.Format = string.Empty;
node.Mode = ResizeMode.Max;
var result = node.Execute(args);
var log = logger.ToString();
Assert.AreEqual(1, result);
}
[TestMethod]
public void ImageNodes_Basic_Resize_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty, null!)
{
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()
{
var args = new NodeParameters(TestImage1, new TestLogger(), false, string.Empty, null!)
{
TempPath = TempDir
};
var imgFile = new ImageFile();
imgFile.Execute(args);
int width = imgFile.GetImageInfo(args)?.Width ?? 0;
int height = imgFile.GetImageInfo(args)?.Height ?? 0;
var node = new ImageResizer();
node.Width = 200;
node.Height = 50;
node.Percent = true;
node.Mode = ResizeMode.Fill;
Assert.AreEqual(1, node.Execute(args));
var img = node.GetImageInfo(args);
Assert.IsNotNull(img);
Assert.AreEqual(width * 2, img.Width);
Assert.AreEqual(height / 2, img.Height);
}
[TestMethod]
public void ImageNodes_Basic_Flip()
{
var args = new NodeParameters(TestImage2, new TestLogger(), false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new ImageFlip();
node.Vertical = false;
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Flip_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new ImageFlip();
node.Vertical = false;
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Rotate()
{
var args = new NodeParameters(TestImage2, new TestLogger(), false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new ImageRotate();
node.Angle = 270;
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_Rotate_Heic()
{
var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new ImageRotate();
node.Angle = 270;
Assert.AreEqual(1, node.Execute(args));
}
[TestMethod]
public void ImageNodes_Basic_AutoCrop_01()
{
Assert.IsTrue(System.IO.File.Exists(TestCropImage1));
var logger = new TestLogger();
var args = new NodeParameters(TestCropImage1, logger, false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new AutoCropImage();
node.Threshold = 30;
node.PreExecute(args);
int result = node.Execute(args);
string log = logger.ToString();
Assert.AreEqual(1, result);
}
[TestMethod]
public void ImageNodes_Basic_AutoCrop_02()
{
var logger = new TestLogger();
var args = new NodeParameters(TestCropImage2, logger, false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new AutoCropImage();
node.Threshold = 95;
int result = node.Execute(args);
string log = logger.ToString();
Assert.AreEqual(1, result);
}
[TestMethod]
public void ImageNodes_Basic_AutoCrop_03()
{
var logger = new TestLogger();
var args = new NodeParameters(TestCropImage3, logger, false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new AutoCropImage();
node.Threshold = 30;
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, null!)
{
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();
Assert.AreEqual(1, result);
}
[TestMethod]
public void ImageNodes_Basic_AutoCrop_NoCrop()
{
var logger = new TestLogger();
var args = new NodeParameters(TestCropImageNoCrop, logger, false, string.Empty, null!)
{
TempPath = TempDir
};
var node = new AutoCropImage();
node.Threshold = 50;
node.PreExecute(args);
int result = node.Execute(args);
string log = logger.ToString();
Assert.AreEqual(2, result);
}
[TestMethod]
public void ImageNodes_Basic_Exif()
{
var logger = new TestLogger();
var args = new NodeParameters(TestExif, logger, false, string.Empty, new LocalFileService())
{
TempPath = TempDir
};
var node = new ImageFile();
var result = node.Execute(args);
Assert.AreEqual(1, result);
if(node.Variables.TryGetValue("img.DateTaken", out object? oDate) == false)
Assert.Fail("Failed to get date time");
if(oDate is DateTime dt == false)
Assert.Fail("oDate is not a DateTime");
}
}
#endif
// #if(DEBUG)
//
// using FileFlows.ImageNodes.Images;
// using Microsoft.VisualStudio.TestTools.UnitTesting;
// using System.Runtime.InteropServices;
// using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
//
// namespace FileFlows.ImageNodes.Tests;
//
// [TestClass]
// public class ImageNodesTests
// {
// string? TestImage1;
// string? TestImage2;
// string? TestImageHeic;
// string TempDir;
// string? TestCropImage1, TestCropImage2, TestCropImage3, TestCropImage4, TestCropImageNoCrop, TestExif;
//
// public ImageNodesTests()
// {
// bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// if (isWindows)
// {
// TestImage1 = @"D:\videos\pictures\image1.jpg";
// TestImage2 = @"D:\videos\pictures\image2.png";
// TempDir = @"D:\videos\temp";
// 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
// {
// 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/circle.jpg";
// TestImage2 = "/home/john/Pictures/36410427.png";
// TempDir = "/home/john/temp/";
// TestExif = "/home/john/Pictures/exif_test.jpg";
// }
// }
//
// [TestMethod]
// public void ImageNodes_Basic_ImageFormat()
// {
// var args = new NodeParameters(TestImage1, new TestLogger(), false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new ImageFormat();
// node.Format = IMAGE_FORMAT_GIF;
// Assert.AreEqual(1, node.Execute(args));
// }
//
// [TestMethod]
// public void ImageNodes_Basic_ImageFormat_Heic()
// {
// var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty, null!)
// {
// 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, null!)
// {
// 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()
// {
// var logger = new TestLogger();
// var args = new NodeParameters(TestImage1, logger, false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new ImageResizer();
// node.Width = 1920;
// node.Height = 1080;
// node.Format = string.Empty;
// node.Mode = ResizeMode.Max;
// var result = node.Execute(args);
// var log = logger.ToString();
// Assert.AreEqual(1, result);
// }
//
// [TestMethod]
// public void ImageNodes_Basic_Resize_Heic()
// {
// var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty, null!)
// {
// 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()
// {
// var args = new NodeParameters(TestImage1, new TestLogger(), false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var imgFile = new ImageFile();
// imgFile.Execute(args);
// int width = imgFile.GetImageInfo(args)?.Width ?? 0;
// int height = imgFile.GetImageInfo(args)?.Height ?? 0;
//
// var node = new ImageResizer();
// node.Width = 200;
// node.Height = 50;
// node.Percent = true;
// node.Mode = ResizeMode.Fill;
// Assert.AreEqual(1, node.Execute(args));
// var img = node.GetImageInfo(args);
// Assert.IsNotNull(img);
// Assert.AreEqual(width * 2, img.Width);
// Assert.AreEqual(height / 2, img.Height);
// }
//
// [TestMethod]
// public void ImageNodes_Basic_Flip()
// {
// var args = new NodeParameters(TestImage2, new TestLogger(), false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new ImageFlip();
// node.Vertical = false;
// Assert.AreEqual(1, node.Execute(args));
// }
//
//
// [TestMethod]
// public void ImageNodes_Basic_Flip_Heic()
// {
// var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new ImageFlip();
// node.Vertical = false;
// Assert.AreEqual(1, node.Execute(args));
// }
//
// [TestMethod]
// public void ImageNodes_Basic_Rotate()
// {
// var args = new NodeParameters(TestImage2, new TestLogger(), false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new ImageRotate();
// node.Angle = 270;
// Assert.AreEqual(1, node.Execute(args));
// }
//
// [TestMethod]
// public void ImageNodes_Basic_Rotate_Heic()
// {
// var args = new NodeParameters(TestImageHeic, new TestLogger(), false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new ImageRotate();
// node.Angle = 270;
// Assert.AreEqual(1, node.Execute(args));
// }
//
// [TestMethod]
// public void ImageNodes_Basic_AutoCrop_01()
// {
// Assert.IsTrue(System.IO.File.Exists(TestCropImage1));
// var logger = new TestLogger();
// var args = new NodeParameters(TestCropImage1, logger, false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new AutoCropImage();
// node.Threshold = 30;
// node.PreExecute(args);
// int result = node.Execute(args);
//
// string log = logger.ToString();
// Assert.AreEqual(1, result);
// }
//
// [TestMethod]
// public void ImageNodes_Basic_AutoCrop_02()
// {
// var logger = new TestLogger();
// var args = new NodeParameters(TestCropImage2, logger, false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new AutoCropImage();
// node.Threshold = 95;
// int result = node.Execute(args);
//
// string log = logger.ToString();
// Assert.AreEqual(1, result);
// }
//
// [TestMethod]
// public void ImageNodes_Basic_AutoCrop_03()
// {
// var logger = new TestLogger();
// var args = new NodeParameters(TestCropImage3, logger, false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new AutoCropImage();
// node.Threshold = 30;
// 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, null!)
// {
// 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();
// Assert.AreEqual(1, result);
// }
//
// [TestMethod]
// public void ImageNodes_Basic_AutoCrop_NoCrop()
// {
// var logger = new TestLogger();
// var args = new NodeParameters(TestCropImageNoCrop, logger, false, string.Empty, null!)
// {
// TempPath = TempDir
// };
//
// var node = new AutoCropImage();
// node.Threshold = 50;
// node.PreExecute(args);
// int result = node.Execute(args);
//
// string log = logger.ToString();
// Assert.AreEqual(2, result);
// }
//
//
// [TestMethod]
// public void ImageNodes_Basic_Exif()
// {
// var logger = new TestLogger();
// var args = new NodeParameters(TestExif, logger, false, string.Empty, new LocalFileService())
// {
// TempPath = TempDir
// };
//
// var node = new ImageFile();
// var result = node.Execute(args);
// Assert.AreEqual(1, result);
// if(node.Variables.TryGetValue("img.DateTaken", out object? oDate) == false)
// Assert.Fail("Failed to get date time");
//
// if(oDate is DateTime dt == false)
// Assert.Fail("oDate is not a DateTime");
// }
// }
//
// #endif

View File

@@ -68,9 +68,7 @@
"Width": "Width",
"Height": "Height",
"Mode": "Mode",
"Mode-Help": "The mode to use when resizing the image",
"Percent": "Percent",
"Percent-Help": "When selected, the width and height values become percentages, with 100 being 100%"
"Mode-Help": "The mode to use when resizing the image"
}
},
"ImageRotate": {