mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-01-06 13:20:29 -06:00
FF-1536: Redeveloped Image Plugin
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace FileFlows.ImageNodes.Images;
|
||||
|
||||
public enum ResizeMode
|
||||
{
|
||||
Fill = 1,
|
||||
Contain = 2,
|
||||
Cover = 3,
|
||||
None = 4,
|
||||
Min = 5,
|
||||
Max = 6,
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
38
ImageNodes/Images/ImageConvert.cs
Normal file
38
ImageNodes/Images/ImageConvert.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user