mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
v2.13.0
- (Add) Tool - Light bleed compensation: Compensate the over-curing and light bleed from clear resins by dimming the sequential pixels - (Add) Infill: Honeycomb infill type - (Upgrade) MessageBox from 1.2.0 to 1.3.1 to fix the small size messages
This commit is contained in:
+7
-1
@@ -1,6 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## /05/2021 - v2.12.2
|
||||
## 23/05/2021 - v2.13.0
|
||||
|
||||
- (Add) Tool - Light bleed compensation: Compensate the over-curing and light bleed from clear resins by dimming the sequential pixels
|
||||
- (Add) Infill: Honeycomb infill type
|
||||
- (Upgrade) MessageBox from 1.2.0 to 1.3.1 to fix the small size messages
|
||||
|
||||
## 20/05/2021 - v2.12.2
|
||||
|
||||
- (Add) Layer action - Export layers to heat map: Export a layer range to a grayscale heat map image that represents the median of the mass in the Z depth/perception. The pixel brightness/intensity shows where the most mass are concentrated.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace UVtools.Core.Extensions
|
||||
|
||||
public static Point Rotate(this Point point, double angleDegree, Point pivot = default)
|
||||
{
|
||||
if (angleDegree == 0 || angleDegree == 360) return point;
|
||||
if (angleDegree is 0 or 360) return point;
|
||||
double angle = angleDegree * Math.PI / 180;
|
||||
double cos = Math.Cos(angle);
|
||||
double sin = Math.Sin(angle);
|
||||
|
||||
@@ -383,6 +383,14 @@ namespace UVtools.Core.Operations
|
||||
ROI = roi;
|
||||
}
|
||||
|
||||
public Size GetRoiSizeOrDefault() => GetRoiSizeOrDefault(SlicerFile.Resolution);
|
||||
public Size GetRoiSizeOrDefault(Mat defaultMat) => GetRoiSizeOrDefault(defaultMat.Size);
|
||||
|
||||
public Size GetRoiSizeOrDefault(Size defaultSize)
|
||||
{
|
||||
return HaveROI && defaultSize != _roi.Size ? _roi.Size : defaultSize;
|
||||
}
|
||||
|
||||
public Mat GetRoiOrDefault(Mat defaultMat)
|
||||
{
|
||||
return HaveROI && defaultMat.Size != _roi.Size ? new Mat(defaultMat, _roi) : defaultMat;
|
||||
|
||||
@@ -21,15 +21,17 @@ namespace UVtools.Core.Operations
|
||||
[Serializable]
|
||||
public sealed class OperationBlur : Operation
|
||||
{
|
||||
#region Members
|
||||
private BlurAlgorithm _blurOperation = BlurAlgorithm.Blur;
|
||||
private uint _size = 1;
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
public override string Title => "Blur";
|
||||
public override string Description =>
|
||||
$"Blur layer images by applying a low pass filter\n\n" +
|
||||
"NOTE: Target printer must support AntiAliasing in order to use this function.\n\n" +
|
||||
$"Blur layer images by applying a low pass filter.\n\n" +
|
||||
"NOTE: Target printer must support AntiAliasing in order to use this function.\n" +
|
||||
"See https://docs.opencv.org/master/d4/d13/tutorial_py_filtering.html";
|
||||
|
||||
public override string ConfirmationText =>
|
||||
|
||||
@@ -12,7 +12,6 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Emgu.CV;
|
||||
using UVtools.Core.FileFormats;
|
||||
using UVtools.Core.Objects;
|
||||
|
||||
namespace UVtools.Core.Operations
|
||||
{
|
||||
@@ -58,7 +57,7 @@ namespace UVtools.Core.Operations
|
||||
public override bool CanROI => false;
|
||||
public override string Title => "Change print resolution";
|
||||
public override string Description =>
|
||||
"Crops or resizes all layer images to fit an alternate print resolution\n" +
|
||||
"Crops or resizes all layer images to fit an alternate print resolution.\n" +
|
||||
"Useful to make files printable on a different printer than they were originally sliced for without the need to re-slice.\n\n" +
|
||||
"NOTE: Please ensure that the actual model will fit within the new print resolution. The operation will be aborted if it will result in any of the actual model being clipped.\n" +
|
||||
"Only use this tool if both source and target printer have the same pixel pitch spec, otherwise the model size will be invalidated and result in a different size than the originally sliced for. " +
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace UVtools.Core.Operations
|
||||
public override string Title => "Dynamic lifts";
|
||||
|
||||
public override string Description =>
|
||||
"Generate dynamic lift height and speeds for each layer given it mass\n" +
|
||||
"Generate dynamic lift height and speeds for each layer given it mass.\n" +
|
||||
"Larger masses requires more lift height and less speed while smaller masses can go with shorter lift height and more speed.\n" +
|
||||
"If you have a raft, start after it layer number to not influence the calculations.\n" +
|
||||
"Note: Only few printers support this. Running this on an unsupported printer will cause no harm.";
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace UVtools.Core.Operations
|
||||
public override string Title => "Infill";
|
||||
|
||||
public override string Description =>
|
||||
$"Generate infill patterns in the model\n\nNOTES:\n1) You must exclude floor and ceil layers from the range.\n2) You must take care of drain holes after the operation.";
|
||||
$"Generate infill patterns in the model.\n\nNOTES:\n1) You must exclude floor and ceil layers from the range.\n2) You must take care of drain holes after the operation.";
|
||||
|
||||
public override string ConfirmationText =>
|
||||
$"infill model with {InfillType} from layers {LayerIndexStart} through {LayerIndexEnd}?";
|
||||
@@ -53,6 +53,7 @@ namespace UVtools.Core.Operations
|
||||
CubicCenterLink,
|
||||
CubicDynamicLink,
|
||||
CubicInterlinked,
|
||||
Honeycomb
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -128,17 +129,23 @@ namespace UVtools.Core.Operations
|
||||
|
||||
protected override bool ExecuteInternally(OperationProgress progress)
|
||||
{
|
||||
Mat mask = null;
|
||||
if (_infillType == InfillAlgorithm.Honeycomb)
|
||||
{
|
||||
mask = GetHoneycombMask(GetRoiSizeOrDefault());
|
||||
}
|
||||
|
||||
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex =>
|
||||
{
|
||||
if (progress.Token.IsCancellationRequested) return;
|
||||
|
||||
using var mat = SlicerFile[layerIndex].LayerMat;
|
||||
Execute(mat, layerIndex);
|
||||
Execute(mat, layerIndex, mask);
|
||||
SlicerFile[layerIndex].LayerMat = mat;
|
||||
|
||||
progress.LockAndIncrement();
|
||||
});
|
||||
|
||||
mask?.Dispose();
|
||||
return !progress.Token.IsCancellationRequested;
|
||||
}
|
||||
|
||||
@@ -149,22 +156,22 @@ namespace UVtools.Core.Operations
|
||||
var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor);
|
||||
uint index = Convert.ToUInt32(arguments[0]);
|
||||
uint layerIndex = index - LayerIndexStart;
|
||||
var infillColor = new MCvScalar(InfillBrightness);
|
||||
var infillColor = new MCvScalar(_infillBrightness);
|
||||
|
||||
Mat patternMask = null;
|
||||
using Mat erode = new ();
|
||||
using Mat diff = new ();
|
||||
Mat target = GetRoiOrDefault(mat);
|
||||
var target = GetRoiOrDefault(mat);
|
||||
using var mask = GetMask(mat);
|
||||
|
||||
bool disposeTargetMask = true;
|
||||
|
||||
if (InfillType == InfillAlgorithm.Cubic ||
|
||||
InfillType == InfillAlgorithm.CubicCenterLink ||
|
||||
InfillType == InfillAlgorithm.CubicDynamicLink ||
|
||||
InfillType == InfillAlgorithm.CubicInterlinked)
|
||||
if (_infillType is InfillAlgorithm.Cubic
|
||||
or InfillAlgorithm.CubicCenterLink
|
||||
or InfillAlgorithm.CubicDynamicLink
|
||||
or InfillAlgorithm.CubicInterlinked)
|
||||
{
|
||||
using var infillPattern = EmguExtensions.InitMat(new Size(InfillSpacing, InfillSpacing));
|
||||
using Mat matPattern = mat.CloneBlank();
|
||||
using var infillPattern = EmguExtensions.InitMat(new Size(_infillSpacing, _infillSpacing));
|
||||
using var matPattern = mat.CloneBlank();
|
||||
bool firstPattern = true;
|
||||
uint accumulator = 0;
|
||||
bool dynamicCenter = false;
|
||||
@@ -172,16 +179,16 @@ namespace UVtools.Core.Operations
|
||||
{
|
||||
dynamicCenter = !dynamicCenter;
|
||||
firstPattern = true;
|
||||
accumulator += InfillSpacing;
|
||||
accumulator += _infillSpacing;
|
||||
|
||||
if (accumulator >= layerIndex) break;
|
||||
firstPattern = false;
|
||||
accumulator += InfillThickness;
|
||||
accumulator += _infillThickness;
|
||||
}
|
||||
|
||||
if (firstPattern)
|
||||
{
|
||||
int thickness = InfillThickness / 2;
|
||||
int thickness = _infillThickness / 2;
|
||||
// Top Left
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(0, 0, thickness, thickness),
|
||||
@@ -207,106 +214,159 @@ namespace UVtools.Core.Operations
|
||||
int margin = (int) (InfillSpacing - accumulator + layerIndex) - thickness;
|
||||
int marginInv = (int) (accumulator - layerIndex) - thickness;
|
||||
|
||||
if (InfillType == InfillAlgorithm.CubicCenterLink ||
|
||||
(InfillType == InfillAlgorithm.CubicDynamicLink &&
|
||||
if (_infillType == InfillAlgorithm.CubicCenterLink ||
|
||||
(_infillType == InfillAlgorithm.CubicDynamicLink &&
|
||||
dynamicCenter) ||
|
||||
InfillType == InfillAlgorithm.CubicInterlinked)
|
||||
_infillType == InfillAlgorithm.CubicInterlinked)
|
||||
{
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(margin, margin, InfillThickness, InfillThickness),
|
||||
new Rectangle(margin, margin, _infillThickness, _infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(marginInv, marginInv, InfillThickness,
|
||||
InfillThickness),
|
||||
new Rectangle(marginInv, marginInv, _infillThickness,
|
||||
_infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(margin, marginInv, InfillThickness,
|
||||
InfillThickness),
|
||||
new Rectangle(margin, marginInv, _infillThickness,
|
||||
_infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(marginInv, margin, InfillThickness,
|
||||
InfillThickness),
|
||||
new Rectangle(marginInv, margin, _infillThickness,
|
||||
_infillThickness),
|
||||
infillColor, -1);
|
||||
}
|
||||
|
||||
|
||||
if (InfillType == InfillAlgorithm.CubicInterlinked ||
|
||||
(InfillType == InfillAlgorithm.CubicDynamicLink &&
|
||||
if (_infillType == InfillAlgorithm.CubicInterlinked ||
|
||||
(_infillType == InfillAlgorithm.CubicDynamicLink &&
|
||||
!dynamicCenter))
|
||||
{
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(margin, -thickness, InfillThickness,
|
||||
InfillThickness),
|
||||
new Rectangle(margin, -thickness, _infillThickness,
|
||||
_infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(marginInv, -thickness, InfillThickness,
|
||||
InfillThickness),
|
||||
new Rectangle(marginInv, -thickness, _infillThickness,
|
||||
_infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(-thickness, margin, InfillThickness,
|
||||
InfillThickness),
|
||||
new Rectangle(-thickness, margin, _infillThickness,
|
||||
_infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(-thickness, marginInv, InfillThickness,
|
||||
InfillThickness),
|
||||
new Rectangle(-thickness, marginInv, _infillThickness,
|
||||
_infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(InfillSpacing - thickness, margin,
|
||||
InfillThickness, InfillThickness),
|
||||
_infillThickness, _infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(InfillSpacing - thickness, marginInv,
|
||||
InfillThickness, InfillThickness),
|
||||
_infillThickness, _infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(margin, InfillSpacing - thickness,
|
||||
InfillThickness, InfillThickness),
|
||||
_infillThickness, _infillThickness),
|
||||
infillColor, -1);
|
||||
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(marginInv, InfillSpacing - thickness,
|
||||
InfillThickness, InfillThickness),
|
||||
_infillThickness, _infillThickness),
|
||||
infillColor, -1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
CvInvoke.Rectangle(infillPattern,
|
||||
new Rectangle(0, 0, InfillSpacing, InfillSpacing),
|
||||
infillColor, InfillThickness);
|
||||
new Rectangle(0, 0, _infillSpacing, _infillSpacing),
|
||||
infillColor, _infillThickness);
|
||||
}
|
||||
|
||||
|
||||
CvInvoke.Repeat(infillPattern, target.Rows / infillPattern.Rows + 1,
|
||||
target.Cols / infillPattern.Cols + 1, matPattern);
|
||||
patternMask = new Mat(matPattern, new Rectangle(0, 0, target.Width, target.Height));
|
||||
disposeTargetMask = true;
|
||||
}
|
||||
else if (_infillType == InfillAlgorithm.Honeycomb)
|
||||
{
|
||||
if (arguments.Length >= 2)
|
||||
{
|
||||
CvInvoke.Repeat(infillPattern, target.Rows / infillPattern.Rows + 1,
|
||||
target.Cols / infillPattern.Cols + 1, matPattern);
|
||||
patternMask = new Mat(matPattern, new Rectangle(0, 0, target.Width, target.Height));
|
||||
patternMask = (Mat)arguments[1];
|
||||
disposeTargetMask = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
patternMask = GetHoneycombMask(target.Size);
|
||||
disposeTargetMask = true;
|
||||
}
|
||||
}
|
||||
|
||||
//patternMask.Save("D:\\pattern.png");
|
||||
CvInvoke.Erode(target, erode, kernel, anchor, WallThickness, BorderType.Reflect101,
|
||||
default);
|
||||
CvInvoke.Subtract(target, erode, diff);
|
||||
|
||||
|
||||
CvInvoke.BitwiseAnd(erode, patternMask, target, mask);
|
||||
CvInvoke.Add(target, diff, target, mask);
|
||||
patternMask?.Dispose();
|
||||
|
||||
if (disposeTargetMask)
|
||||
{
|
||||
patternMask.Dispose();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Mat GetHoneycombMask(Size targetSize)
|
||||
{
|
||||
var patternMask = EmguExtensions.InitMat(targetSize);
|
||||
|
||||
var halfInfillSpacing = _infillSpacing / 2;
|
||||
var halfThickenss = _infillThickness / 2;
|
||||
int width = (int)Math.Round(4 * (_infillSpacing / 2.0 / Math.Sqrt(3)));
|
||||
var infillColor = new MCvScalar(_infillBrightness);
|
||||
|
||||
for (int col = 0; col <= targetSize.Width / _infillSpacing; col++)
|
||||
{
|
||||
for (int row = 0; row <= targetSize.Height / _infillSpacing; row++)
|
||||
{
|
||||
// Move over for the column number.
|
||||
int x = (int)Math.Round(col * (width * 0.75f));
|
||||
|
||||
// Move down the required number of rows.
|
||||
int y = row * _infillSpacing;
|
||||
|
||||
// If the column is odd, move down half a hex more.
|
||||
if (col % 2 == 1) y += halfInfillSpacing;
|
||||
|
||||
var points = new Point[]
|
||||
{
|
||||
new(x, y),
|
||||
new((int) Math.Round(x + width * 0.25f), y - _infillSpacing / 2),
|
||||
new((int) Math.Round(x + width * 0.75f), y - _infillSpacing / 2),
|
||||
new(x + width, y),
|
||||
new((int) Math.Round(x + width * 0.75f), y + _infillSpacing / 2),
|
||||
new((int) Math.Round(x + width * 0.25f), y + _infillSpacing / 2),
|
||||
};
|
||||
|
||||
CvInvoke.Polylines(patternMask, points, true, infillColor, _infillThickness);
|
||||
}
|
||||
}
|
||||
|
||||
return patternMask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace UVtools.Core.Operations
|
||||
public override string Title => "Export layers to GIF";
|
||||
|
||||
public override string Description =>
|
||||
"Export a layer range to an animated GIF file\n" +
|
||||
"Export a layer range to an animated GIF file.\n" +
|
||||
"Note: This process is slow, optimize the parameters to output few layers as possible and/or scale them down.";
|
||||
|
||||
public override string ConfirmationText =>
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace UVtools.Core.Operations
|
||||
#region Overrides
|
||||
|
||||
public override bool CanHaveProfiles => false;
|
||||
public override string Title => "Export layers to heat map";
|
||||
public override string Title => "Export layers to heat map.";
|
||||
|
||||
public override string Description =>
|
||||
"Export a layer range to a grayscale heat map image that represents the median of the mass in the Z depth/perception\n" +
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace UVtools.Core.Operations
|
||||
Average
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Members
|
||||
private OperationLayerReHeightItem _selectedItem;
|
||||
private OperationLayerReHeightAntiAliasingType _antiAliasingType;
|
||||
@@ -46,7 +47,7 @@ namespace UVtools.Core.Operations
|
||||
public override bool CanROI => false;
|
||||
public override string Title => "Adjust layer height";
|
||||
public override string Description =>
|
||||
"Adjust the layer height of the model\n\n" +
|
||||
"Adjust the layer height of the model.\n\n" +
|
||||
"Adjusting to values lower than current height will reduce layer lines, adjusting to values higher" +
|
||||
" than current height will reduce model detail.\n\n" +
|
||||
"Note: Using dedicated slicer software to re-slice will usually yeild better results.";
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.Structure;
|
||||
using UVtools.Core.Extensions;
|
||||
using UVtools.Core.FileFormats;
|
||||
|
||||
namespace UVtools.Core.Operations
|
||||
{
|
||||
[Serializable]
|
||||
public class OperationLightBleedCompensation : Operation
|
||||
{
|
||||
#region Enums
|
||||
|
||||
public enum LightBleedCompensationLookupMode : byte
|
||||
{
|
||||
[Description("Previous: Look for sequential pixels relative to the previous layers")]
|
||||
Previous,
|
||||
|
||||
[Description("Next: Look for sequential pixels relative to the next layers")]
|
||||
Next,
|
||||
|
||||
[Description("Both: Look for sequential pixels relative to the previous and next layers")]
|
||||
Both
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Members
|
||||
|
||||
private LightBleedCompensationLookupMode _lookupMode = LightBleedCompensationLookupMode.Next;
|
||||
private string _dimBy = "25,15,10,5";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.Normal;
|
||||
|
||||
public override string Title => "Light bleed compensation";
|
||||
public override string Description =>
|
||||
"Compensate the over-curing and light bleed from clear resins by dimming the sequential pixels.\n" +
|
||||
"Note: You need to find the optimal minimum pixel brightness that such resin can print in order to optimize this process.\n" +
|
||||
"With more translucent resins you can go with lower brightness but stick to a limit that can form the layer without loss." +
|
||||
" Tiny details can be lost when using low brightness level.\n" +
|
||||
"After apply a light bleed compensation, do not apply or re-run this tool over.";
|
||||
|
||||
public override string ConfirmationText =>
|
||||
$"compensate layers {LayerIndexStart} through {LayerIndexEnd}?";
|
||||
|
||||
public override string ProgressTitle =>
|
||||
$"Compensate layers {LayerIndexStart} through {LayerIndexEnd}";
|
||||
|
||||
public override string ProgressAction => "Compensated layers";
|
||||
|
||||
public override string ValidateInternally()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
|
||||
if (DimByArray.Length == 0)
|
||||
{
|
||||
sb.AppendLine($"The dim levels are invalid or not set.");
|
||||
}
|
||||
|
||||
if (MaximumSubtraction >= byte.MaxValue)
|
||||
{
|
||||
sb.AppendLine($"The sum of dim levels are producing black pixels.");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var result = $"[Lookup: {_lookupMode}]" +
|
||||
$" [Dim by: {_dimBy}]" + LayerRangeString;
|
||||
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public OperationLightBleedCompensation() { }
|
||||
|
||||
public OperationLightBleedCompensation(FileFormat slicerFile) : base(slicerFile) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public LightBleedCompensationLookupMode LookupMode
|
||||
{
|
||||
get => _lookupMode;
|
||||
set => RaiseAndSetIfChanged(ref _lookupMode, value);
|
||||
}
|
||||
|
||||
public string DimBy
|
||||
{
|
||||
get => _dimBy;
|
||||
set
|
||||
{
|
||||
if(!RaiseAndSetIfChanged(ref _dimBy, value)) return;
|
||||
RaisePropertyChanged(nameof(MinimumBrightness));
|
||||
RaisePropertyChanged(nameof(MaximumSubtraction));
|
||||
}
|
||||
}
|
||||
|
||||
public int MinimumBrightness => 255 - MaximumSubtraction;
|
||||
public int MaximumSubtraction => DimByArray.Aggregate(0, (current, dim) => current + dim);
|
||||
|
||||
public byte[] DimByArray
|
||||
{
|
||||
get
|
||||
{
|
||||
List<byte> levels = new();
|
||||
var split = _dimBy.Split(',', StringSplitOptions.TrimEntries);
|
||||
foreach (var str in split)
|
||||
{
|
||||
if (!byte.TryParse(str, out var brightness)) continue;
|
||||
if (brightness is byte.MinValue or byte.MaxValue) continue;
|
||||
levels.Add(brightness);
|
||||
}
|
||||
|
||||
return levels.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public MCvScalar[] DimByMCvScalar
|
||||
{
|
||||
get
|
||||
{
|
||||
List<MCvScalar> levels = new();
|
||||
var split = _dimBy.Split(',', StringSplitOptions.TrimEntries);
|
||||
foreach (var str in split)
|
||||
{
|
||||
if (!byte.TryParse(str, out var brightness)) continue;
|
||||
if (brightness is byte.MinValue or byte.MaxValue) continue;
|
||||
levels.Add(new MCvScalar(brightness));
|
||||
}
|
||||
|
||||
return levels.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equality
|
||||
|
||||
protected bool Equals(OperationLightBleedCompensation other)
|
||||
{
|
||||
return _lookupMode == other._lookupMode && _dimBy == other._dimBy;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((OperationLightBleedCompensation) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine((int) _lookupMode, _dimBy);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get the cached dim mat's
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Mat[] GetDimMats()
|
||||
{
|
||||
var dimLevels = DimByMCvScalar;
|
||||
if (dimLevels.Length == 0) return Array.Empty<Mat>();
|
||||
var mats = new Mat[dimLevels.Length];
|
||||
var matSize = GetRoiSizeOrDefault();
|
||||
for (var i = 0; i < mats.Length; i++)
|
||||
{
|
||||
mats[i] = EmguExtensions.InitMat(matSize, dimLevels[i]);
|
||||
}
|
||||
|
||||
return mats;
|
||||
}
|
||||
|
||||
protected override bool ExecuteInternally(OperationProgress progress)
|
||||
{
|
||||
var dimMats = GetDimMats();
|
||||
if (dimMats.Length == 0) return false;
|
||||
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex =>
|
||||
{
|
||||
if (progress.Token.IsCancellationRequested) return; // Abort operation, user requested cancellation
|
||||
|
||||
var layer = SlicerFile[layerIndex];
|
||||
using var mat = layer.LayerMat;
|
||||
using var original = mat.Clone();
|
||||
var target = GetRoiOrDefault(mat);
|
||||
|
||||
for (byte i = 0; i < dimMats.Length; i++)
|
||||
{
|
||||
Mat mask = null;
|
||||
Mat previousMat = null;
|
||||
Mat previousMatRoi = null;
|
||||
Mat nextMat = null;
|
||||
Mat nextMatRoi = null;
|
||||
|
||||
|
||||
if (_lookupMode is LightBleedCompensationLookupMode.Previous or LightBleedCompensationLookupMode.Both)
|
||||
{
|
||||
int layerPreviousIndex = (int)layerIndex - i - 1;
|
||||
if (layerPreviousIndex >= LayerIndexStart)
|
||||
{
|
||||
previousMat = SlicerFile[layerPreviousIndex].LayerMat;
|
||||
mask = previousMatRoi = GetRoiOrDefault(previousMat);
|
||||
}
|
||||
}
|
||||
if (_lookupMode is LightBleedCompensationLookupMode.Next or LightBleedCompensationLookupMode.Both)
|
||||
{
|
||||
uint layerIndexNext = (uint) (layerIndex + i + 1);
|
||||
if (layerIndexNext <= LayerIndexEnd)
|
||||
{
|
||||
nextMat = SlicerFile[layerIndexNext].LayerMat;
|
||||
mask = nextMatRoi = GetRoiOrDefault(nextMat);
|
||||
}
|
||||
}
|
||||
|
||||
if (previousMat is null && nextMat is null) break; // Nothing more to do
|
||||
if (previousMat is not null && nextMat is not null) // both, need to merge previous with next layer
|
||||
{
|
||||
CvInvoke.Add(previousMatRoi, nextMatRoi, previousMatRoi);
|
||||
mask = previousMatRoi;
|
||||
}
|
||||
|
||||
CvInvoke.Subtract(target, dimMats[i], target, mask);
|
||||
|
||||
previousMat?.Dispose();
|
||||
nextMat?.Dispose();
|
||||
}
|
||||
|
||||
// Apply the results only to the selected masked area, if user selected one
|
||||
ApplyMask(original, target);
|
||||
|
||||
// Set current layer image with the modified mat we just manipulated
|
||||
layer.LayerMat = mat;
|
||||
|
||||
// Increment progress bar by 1
|
||||
progress.LockAndIncrement();
|
||||
});
|
||||
|
||||
foreach (var dimMat in dimMats)
|
||||
{
|
||||
dimMat.Dispose();
|
||||
}
|
||||
|
||||
// return true if not cancelled by user
|
||||
return !progress.Token.IsCancellationRequested;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ namespace UVtools.Core.Operations
|
||||
public override string Title => "Pixel arithmetic";
|
||||
|
||||
public override string Description =>
|
||||
"Perform arithmetic operations over the pixels";
|
||||
"Perform arithmetic operations over the pixels.";
|
||||
|
||||
public override string ConfirmationText =>
|
||||
$"arithmetic {_operator}" +
|
||||
|
||||
@@ -12,7 +12,6 @@ using System.Xml.Serialization;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using UVtools.Core.FileFormats;
|
||||
using UVtools.Core.Objects;
|
||||
using UVtools.Core.Scripting;
|
||||
|
||||
namespace UVtools.Core.Operations
|
||||
@@ -36,7 +35,7 @@ namespace UVtools.Core.Operations
|
||||
public override string Title => "Scripting";
|
||||
|
||||
public override string Description =>
|
||||
$"Run external scripts to manipulate the loaded file\n" +
|
||||
$"Run external scripts to manipulate the loaded file.\n" +
|
||||
$"The scripts have wide access to your system and able to do modifications, read/write files, etc. " +
|
||||
$"Make sure to run only the scripts you trust! Or run UVtools in a sandbox while executing this.";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
|
||||
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
|
||||
<Version>2.12.2</Version>
|
||||
<Version>2.13.0</Version>
|
||||
<Copyright>Copyright © 2020 PTRTECH</Copyright>
|
||||
<PackageIcon>UVtools.png</PackageIcon>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
HERE="$(dirname "$(readlink -f "${0}")")"
|
||||
export PATH="${HERE}"/usr/bin/:"${PATH}"
|
||||
EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1)
|
||||
exec "${EXEC}" $@
|
||||
@@ -0,0 +1,12 @@
|
||||
# Desktop Entry Specification: https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
|
||||
# https://github.com/AppImage/AppImageKit/wiki/Bundling-.NET-Core-apps
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=UVtools
|
||||
Comment=MSLA/DLP, file analysis, calibration, repair, conversion and manipulation
|
||||
URL=https://github.com/sn4k3/UVtools
|
||||
Icon=UVtools
|
||||
Exec=UVtools
|
||||
Path=~
|
||||
Terminal=true
|
||||
Categories=Development;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UVtools.Core.Scripting;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.Structure;
|
||||
using UVtools.Core.Extensions;
|
||||
|
||||
namespace UVtools.ScriptSample
|
||||
{
|
||||
/// <summary>
|
||||
/// Performs a black inset around objects
|
||||
/// </summary>
|
||||
public class ScriptLightBleedCompensationSample : ScriptGlobals
|
||||
{
|
||||
ScriptTextBoxInput BrightnessesInput = new()
|
||||
{
|
||||
Label = "Brightnesses",
|
||||
ToolTip = "Brightness to reduce each subsequent repeated pixels",
|
||||
Unit = "1-255",
|
||||
Value = "25,20,15,10,5"
|
||||
};
|
||||
|
||||
|
||||
public byte[] Levels
|
||||
{
|
||||
get
|
||||
{
|
||||
List<byte> levels = new();
|
||||
var split = BrightnessesInput.Value.Split(',', StringSplitOptions.TrimEntries);
|
||||
foreach (var str in split)
|
||||
{
|
||||
if(!byte.TryParse(str, out var brightness)) continue;
|
||||
if(brightness is byte.MinValue or byte.MaxValue) continue;
|
||||
levels.Add(brightness);
|
||||
}
|
||||
|
||||
return levels.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set configurations here, this function trigger just after load a script
|
||||
/// </summary>
|
||||
public void ScriptInit()
|
||||
{
|
||||
Script.Name = "Light bleed compensation";
|
||||
Script.Description = "Dim sequential pixels";
|
||||
Script.Author = "Tiago Conceição";
|
||||
Script.Version = new Version(0, 1);
|
||||
Script.UserInputs.Add(BrightnessesInput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate user inputs here, this function trigger when user click on execute
|
||||
/// </summary>
|
||||
/// <returns>A error message, empty or null if validation passes.</returns>
|
||||
public string ScriptValidate()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
|
||||
if (Levels.Length == 0)
|
||||
{
|
||||
sb.AppendLine($"No brightness levels are set");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the script, this function trigger when when user click on execute and validation passes
|
||||
/// </summary>
|
||||
/// <returns>True if executes successfully to the end, otherwise false.</returns>
|
||||
public bool ScriptExecute()
|
||||
{
|
||||
Progress.Reset("Bleed compensation", Operation.LayerRangeCount); // Sets the progress name and number of items to process
|
||||
var brightnesses = Levels;
|
||||
|
||||
// Loop user selected layers in parallel, this will put each core of CPU working here on parallel
|
||||
Parallel.For(Operation.LayerIndexStart, Operation.LayerIndexEnd+1, 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
|
||||
|
||||
var target = Operation.GetRoiOrDefault(mat); // Get ROI from mat if user selected an region
|
||||
|
||||
for (byte i = 0; i < brightnesses.Length; i++)
|
||||
{
|
||||
uint layerIndexNext = (uint) (layerIndex + i + 1);
|
||||
if (layerIndexNext > Operation.LayerIndexEnd) break;
|
||||
using var subtractMat = EmguExtensions.InitMat(target.Size, new MCvScalar(brightnesses[i]));
|
||||
|
||||
using var nextMat = SlicerFile[layerIndexNext].LayerMat;
|
||||
var nextMatRoi = Operation.GetRoiOrDefault(nextMat);
|
||||
|
||||
CvInvoke.Subtract(target, subtractMat, target, nextMatRoi);
|
||||
}
|
||||
|
||||
// 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 if not cancelled by user
|
||||
return !Progress.Token.IsCancellationRequested;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 199 B |
@@ -0,0 +1,42 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="UVtools.WPF.Controls.Tools.ToolLightBleedCompensationControl">
|
||||
<Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto" ColumnDefinitions="Auto,10,500">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Pixel lookup mode:"/>
|
||||
|
||||
<ComboBox Grid.Row="0" Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Items="{Binding Operation.LookupMode, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
|
||||
SelectedItem="{Binding Operation.LookupMode, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="List of brightnesses to subtract for each subsequent pixel, format: 1-254, 1-254, 1-254, ..."
|
||||
Text="Dim subsequent pixels by:"/>
|
||||
|
||||
<TextBox Grid.Row="2" Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Operation.DimBy}"/>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Minimum pixel brightness:"/>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Operation.MinimumBrightness, StringFormat={}{0} of 255}"/>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Maximum subtraction:"/>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Operation.MaximumSubtraction, StringFormat={}{0} of 255}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,20 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using UVtools.Core.Operations;
|
||||
|
||||
namespace UVtools.WPF.Controls.Tools
|
||||
{
|
||||
public partial class ToolLightBleedCompensationControl : ToolControl
|
||||
{
|
||||
public OperationLightBleedCompensation Operation => BaseOperation as OperationLightBleedCompensation;
|
||||
public ToolLightBleedCompensationControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
BaseOperation = new OperationLightBleedCompensation(SlicerFile);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ namespace UVtools.WPF.Extensions
|
||||
WindowStartupLocation = location,
|
||||
CanResize = false,
|
||||
MaxWidth = window.GetScreenWorkingArea().Width - UserSettings.Instance.General.WindowsHorizontalMargin,
|
||||
MaxHeight = window.GetScreenWorkingArea().Height - UserSettings.Instance.General.WindowsVerticalMargin,
|
||||
SizeToContent = SizeToContent.WidthAndHeight,
|
||||
ShowInCenter = true
|
||||
});
|
||||
return await messageBoxStandardWindow.ShowDialog(window);
|
||||
|
||||
@@ -174,6 +174,14 @@ namespace UVtools.WPF
|
||||
}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Tag = new OperationLightBleedCompensation(),
|
||||
Icon = new Avalonia.Controls.Image
|
||||
{
|
||||
Source = new Bitmap(App.GetAsset("/Assets/Icons/lightbulb-solid-16x16.png"))
|
||||
}
|
||||
},
|
||||
new()
|
||||
{
|
||||
Tag = new OperationInfill(),
|
||||
Icon = new Avalonia.Controls.Image
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<Version>2.12.2</Version>
|
||||
<Version>2.13.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
@@ -30,7 +30,7 @@
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.5" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.5" />
|
||||
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.5.1.4349" />
|
||||
<PackageReference Include="MessageBox.Avalonia" Version="1.2.0" />
|
||||
<PackageReference Include="MessageBox.Avalonia" Version="1.3.1" />
|
||||
<PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -33,8 +33,8 @@ Set-Location $PSScriptRoot\..
|
||||
### Configuration ###
|
||||
####################################
|
||||
$enableMSI = $true
|
||||
#$buildOnly = $null
|
||||
#$buildOnly = "win-x64"
|
||||
#$buildOnly = 'linux-x64'
|
||||
#$buildOnly = 'win-x64'
|
||||
# Profilling
|
||||
$stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch
|
||||
$deployStopWatch = New-Object -TypeName System.Diagnostics.Stopwatch
|
||||
@@ -51,6 +51,11 @@ $objFolder = "$project\obj\$buildWith\$netFolder"
|
||||
$publishFolder = "publish"
|
||||
$platformsFolder = "UVtools.Platforms"
|
||||
|
||||
# Not supported yet! No fuse on WSL
|
||||
$appImageFile = 'appimagetool-x86_64.AppImage'
|
||||
$appImageFilePath = "$platformsFolder/AppImage/$appImageFile"
|
||||
$appImageUrl = "https://github.com/AppImage/AppImageKit/releases/download/continuous/$appImageFile"
|
||||
|
||||
$macIcns = "UVtools.CAD/UVtools.icns"
|
||||
|
||||
#$version = (Get-Command "$releaseFolder\UVtools.dll").FileVersionInfo.ProductVersion
|
||||
@@ -120,6 +125,11 @@ $runtimes =
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# https://github.com/AppImage/AppImageKit/wiki/Bundling-.NET-Core-apps
|
||||
#Invoke-WebRequest -Uri $appImageUrl -OutFile $appImageFilePath
|
||||
#wsl chmod a+x $appImageFilePath
|
||||
|
||||
foreach ($obj in $runtimes.GetEnumerator()) {
|
||||
if(![string]::IsNullOrWhiteSpace($buildOnly) -and !$buildOnly.Equals($obj.Name)) {continue}
|
||||
# Configuration
|
||||
@@ -154,9 +164,13 @@ Building: $runtime"
|
||||
Copy-Item "$platformsFolder\$runtime\$includeObj" -Destination "$publishFolder\$runtime" -Recurse -ErrorAction Ignore
|
||||
}
|
||||
|
||||
Write-Output "Compressing $runtime to: $targetZip"
|
||||
Write-Output $targetZip "$publishFolder/$runtime"
|
||||
|
||||
#if($runtime.Equals('linux-x64')){
|
||||
# $appDirDest = "$publishFolder/AppImage.$runtime/AppDir"
|
||||
# Copy-Item "$platformsFolder/AppImage/AppDir" $appDirDest -Force -Recurse
|
||||
# wsl chmod 755 "$appDirDest/AppRun"
|
||||
# wsl cp -a "$publishFolder/$runtime/." "$appDirDest/usr/bin"
|
||||
# wsl $appImageFilePath $appDirDest
|
||||
#}
|
||||
if($runtime.Equals('osx-x64')){
|
||||
$macAppFolder = "${software}.app"
|
||||
$macPublishFolder = "$publishFolder/${macAppFolder}"
|
||||
@@ -179,6 +193,8 @@ Building: $runtime"
|
||||
|
||||
}
|
||||
else {
|
||||
Write-Output "Compressing $runtime to: $targetZip"
|
||||
Write-Output $targetZip "$publishFolder/$runtime"
|
||||
wsl cd "$publishFolder/$runtime" `&`& pwd `&`& zip -r "../../$targetZip" .
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user