diff --git a/UVtools.ScriptSample/ScriptSample.cs b/UVtools.ScriptSample/ScriptSample.cs new file mode 100644 index 0000000..83f5ca0 --- /dev/null +++ b/UVtools.ScriptSample/ScriptSample.cs @@ -0,0 +1,131 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Drawing; +using System.Text; +using System.Threading.Tasks; +using UVtools.Core.Scripting; +using Emgu.CV; +using Emgu.CV.CvEnum; + +namespace UVtools.ScriptSample +{ + public class ScriptSample : ScriptGlobals + { + ScriptNumericalInput InsetMarginFromEdge = new() + { + Label = "Inset from edge", + ToolTip = "Margin in pixels to inset from object edge", + Unit = "px", + Minimum = 1, + Maximum = ushort.MaxValue, + Increment = 1, + Value = 20 + }; + + ScriptNumericalInput InsetThickness = new() + { + Label = "Inset line thickness", + ToolTip = "Inset line thickness in pixels", + Unit = "px", + Minimum = 1, + Maximum = ushort.MaxValue, + Increment = 1, + Value = 10 + }; + + /// + /// Set configurations here, this function trigger just after load a script + /// + public void ScriptInit() + { + Script.Name = "Inset"; + Script.Description = "Perform a black inset around objects"; + Script.Author = "Tiago Conceição"; + Script.Version = new Version(0, 1); + Script.Inputs.AddRange(new[] + { + InsetMarginFromEdge, + InsetThickness + }); + } + + /// + /// Validate your user inputs here, this function trigger when user click on execute + /// + /// A error message, empty or null if validation passes. + public string ScriptValidate() + { + StringBuilder sb = new(); + + if (InsetMarginFromEdge.Value < InsetMarginFromEdge.Minimum) + { + sb.AppendLine($"Inset edge margin must be at least {InsetMarginFromEdge.Minimum}{InsetMarginFromEdge.Unit}"); + } + if (InsetThickness.Value < InsetThickness.Minimum) + { + sb.AppendLine($"Inset thickness must be at least {InsetThickness.Minimum}{InsetThickness.Unit}"); + } + + return sb.ToString(); + } + + /// + /// Execute the script, this function trigger when when user click on execute and validation passes + /// + /// True if executes successfully to the end, otherwise false. + public bool ScriptExecute() + { + var anchor = new Point(-1, -1); // Kernel anchor, -1, -1 = center + var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); // Rectangle 3x3 kernel + Progress.Reset("Inset layers", Operation.LayerRangeCount); // Sets the progress name and number of items to process + + // Loop user selected layers in parallel, this will put each core of CPU working here on parallel + Parallel.For(Operation.LayerIndexStart, Operation.LayerIndexEnd, layerIndex => + { + if (Progress.Token.IsCancellationRequested) return; // Abort operation, user requested cancellation + + var layer = SlicerFile[layerIndex]; // Unpack and expose layer variable for easier use + using var mat = layer.LayerMat; // Gets this layer mat/image + var original = mat.Clone(); // Keep a original mat copy + using var erodeMat = new Mat(); // Creates a temporary mat for the eroded image + using var wallMat = new Mat(); // Creates a temporary mat for the wall image + + var target = Operation.GetRoiOrDefault(mat); // Get ROI from mat if user selected an region + + // Erode original image by InsetMarginFromEdge pixels, so we get the offset margin from image and put new image on erodeMat + CvInvoke.Erode(target, erodeMat, kernel, anchor, InsetMarginFromEdge.Value, BorderType.Reflect101, default); + + // Now erode the eroded image with InsetThickness pixels, so we get the original-margin-thickness image and put the new image on wallMat + CvInvoke.Erode(erodeMat, wallMat, kernel, anchor, InsetThickness.Value, BorderType.Reflect101, default); + + // Subtract walls image from eroded image, so we get only the inset line pixels in white and put back into wallMat + CvInvoke.Subtract(erodeMat, wallMat, wallMat); + + // Invert pixels of wallMat so the whites will become black and blacks whites + CvInvoke.BitwiseNot(wallMat, wallMat); + + // Bitwise And original image with the modified image and put back into mat + // This will keep only the pixels that are positive in both mat's, so mat[n] & wallMat[n] must both have a positive pixel value (> 0) + CvInvoke.BitwiseAnd(target, wallMat, target); + + // Apply the results only to the selected masked area, if user selected one + Operation.ApplyMask(original, target); + + // Set current layer image with the modified mat we just manipulated + layer.LayerMat = mat; + + // Increment progress bar by 1 + Progress.LockAndIncrement(); + }); + + return true; + } + } +} diff --git a/UVtools.ScriptSample/UVtools.ScriptSample.csproj b/UVtools.ScriptSample/UVtools.ScriptSample.csproj new file mode 100644 index 0000000..d2db2bd --- /dev/null +++ b/UVtools.ScriptSample/UVtools.ScriptSample.csproj @@ -0,0 +1,11 @@ + + + + net5.0 + + + + + + + diff --git a/UVtools.sln b/UVtools.sln index f59ab22..65c5cd1 100644 --- a/UVtools.sln +++ b/UVtools.sln @@ -9,6 +9,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UVtools.GUI", "UVtools.GUI\ EndProjectSection EndProject Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "UVtools.InstallerMM", "UVtools.InstallerMM\UVtools.InstallerMM.wixproj", "{E53BAA5D-29A8-4287-B3AA-1AFF5A4BDC6C}" + ProjectSection(ProjectDependencies) = postProject + {B1B40A7B-D30B-4056-AE6F-C4F0519FD8D4} = {B1B40A7B-D30B-4056-AE6F-C4F0519FD8D4} + EndProjectSection EndProject Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "UVtools.Installer", "UVtools.Installer\UVtools.Installer.wixproj", "{41224896-08E9-4F22-9E56-5F9D46DEC7D2}" EndProject @@ -24,6 +27,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UVtools.WPF", "UVtools.WPF\ {7C9927F8-132E-4A37-B894-440E0FD5AA3D} = {7C9927F8-132E-4A37-B894-440E0FD5AA3D} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UVtools.ScriptSample", "UVtools.ScriptSample\UVtools.ScriptSample.csproj", "{6CCAA18A-8810-42DF-B75B-4160B40B9DD5}" + ProjectSection(ProjectDependencies) = postProject + {7C9927F8-132E-4A37-B894-440E0FD5AA3D} = {7C9927F8-132E-4A37-B894-440E0FD5AA3D} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -98,6 +106,18 @@ Global {B1B40A7B-D30B-4056-AE6F-C4F0519FD8D4}.Release|x64.Build.0 = Release|Any CPU {B1B40A7B-D30B-4056-AE6F-C4F0519FD8D4}.Release|x86.ActiveCfg = Release|Any CPU {B1B40A7B-D30B-4056-AE6F-C4F0519FD8D4}.Release|x86.Build.0 = Release|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Debug|x64.ActiveCfg = Debug|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Debug|x64.Build.0 = Debug|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Debug|x86.ActiveCfg = Debug|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Debug|x86.Build.0 = Debug|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Release|Any CPU.Build.0 = Release|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Release|x64.ActiveCfg = Release|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Release|x64.Build.0 = Release|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Release|x86.ActiveCfg = Release|Any CPU + {6CCAA18A-8810-42DF-B75B-4160B40B9DD5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE