diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-05-24 01:19:58 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-05-24 01:19:58 +0300 |
commit | ff4e0dfe28572b151b4831f905110eeeaf57fb62 (patch) | |
tree | 1b9c363009c3ab0c9dafbcc5f03eb038d0c413f9 | |
parent | ad92a0aa5cef5a9be46e2ba20ac5482fc9030015 (diff) |
v2.13.0v2.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
26 files changed, 652 insertions, 70 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6702ea2..ed9c89d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/UVtools.Core/Extensions/PointExtensions.cs b/UVtools.Core/Extensions/PointExtensions.cs index 4ba31d8..8b0056c 100644 --- a/UVtools.Core/Extensions/PointExtensions.cs +++ b/UVtools.Core/Extensions/PointExtensions.cs @@ -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); diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index f8b9b10..acf78b2 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -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; diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs index 255c57f..9642812 100644 --- a/UVtools.Core/Operations/OperationBlur.cs +++ b/UVtools.Core/Operations/OperationBlur.cs @@ -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 => diff --git a/UVtools.Core/Operations/OperationChangeResolution.cs b/UVtools.Core/Operations/OperationChangeResolution.cs index 4982cd4..c40d3f7 100644 --- a/UVtools.Core/Operations/OperationChangeResolution.cs +++ b/UVtools.Core/Operations/OperationChangeResolution.cs @@ -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. " + diff --git a/UVtools.Core/Operations/OperationDynamicLifts.cs b/UVtools.Core/Operations/OperationDynamicLifts.cs index 7444f0f..5fab1d9 100644 --- a/UVtools.Core/Operations/OperationDynamicLifts.cs +++ b/UVtools.Core/Operations/OperationDynamicLifts.cs @@ -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."; diff --git a/UVtools.Core/Operations/OperationInfill.cs b/UVtools.Core/Operations/OperationInfill.cs index b07ba06..01b3ae9 100644 --- a/UVtools.Core/Operations/OperationInfill.cs +++ b/UVtools.Core/Operations/OperationInfill.cs @@ -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) + { + patternMask = (Mat)arguments[1]; + disposeTargetMask = false; + } + else { - 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 = 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 } } diff --git a/UVtools.Core/Operations/OperationLayerExportGif.cs b/UVtools.Core/Operations/OperationLayerExportGif.cs index e863f89..d0a0d24 100644 --- a/UVtools.Core/Operations/OperationLayerExportGif.cs +++ b/UVtools.Core/Operations/OperationLayerExportGif.cs @@ -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 => diff --git a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs index eb6482c..2c0bccb 100644 --- a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs +++ b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs @@ -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" + diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs index 1aee091..548c25c 100644 --- a/UVtools.Core/Operations/OperationLayerReHeight.cs +++ b/UVtools.Core/Operations/OperationLayerReHeight.cs @@ -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."; diff --git a/UVtools.Core/Operations/OperationLightBleedCompensation.cs b/UVtools.Core/Operations/OperationLightBleedCompensation.cs new file mode 100644 index 0000000..c3175af --- /dev/null +++ b/UVtools.Core/Operations/OperationLightBleedCompensation.cs @@ -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 + } +} diff --git a/UVtools.Core/Operations/OperationPixelArithmetic.cs b/UVtools.Core/Operations/OperationPixelArithmetic.cs index 0ce74e8..427d7be 100644 --- a/UVtools.Core/Operations/OperationPixelArithmetic.cs +++ b/UVtools.Core/Operations/OperationPixelArithmetic.cs @@ -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}" + diff --git a/UVtools.Core/Operations/OperationScripting.cs b/UVtools.Core/Operations/OperationScripting.cs index 661daad..a54326c 100644 --- a/UVtools.Core/Operations/OperationScripting.cs +++ b/UVtools.Core/Operations/OperationScripting.cs @@ -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."; diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 5a6763a..4a97b3d 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -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> diff --git a/UVtools.Platforms/AppImage/AppDir/AppRun b/UVtools.Platforms/AppImage/AppDir/AppRun new file mode 100644 index 0000000..f954dda --- /dev/null +++ b/UVtools.Platforms/AppImage/AppDir/AppRun @@ -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}" $@
\ No newline at end of file diff --git a/UVtools.Platforms/AppImage/AppDir/UVtools.desktop b/UVtools.Platforms/AppImage/AppDir/UVtools.desktop new file mode 100644 index 0000000..70cd65c --- /dev/null +++ b/UVtools.Platforms/AppImage/AppDir/UVtools.desktop @@ -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;
\ No newline at end of file diff --git a/UVtools.Platforms/AppImage/AppDir/UVtools.png b/UVtools.Platforms/AppImage/AppDir/UVtools.png Binary files differnew file mode 100644 index 0000000..a5b52c7 --- /dev/null +++ b/UVtools.Platforms/AppImage/AppDir/UVtools.png diff --git a/UVtools.Platforms/AppImage/appimagetool-x86_64.AppImage b/UVtools.Platforms/AppImage/appimagetool-x86_64.AppImage Binary files differnew file mode 100644 index 0000000..25365df --- /dev/null +++ b/UVtools.Platforms/AppImage/appimagetool-x86_64.AppImage diff --git a/UVtools.ScriptSample/ScriptLightBleedCompensationSample.cs b/UVtools.ScriptSample/ScriptLightBleedCompensationSample.cs new file mode 100644 index 0000000..b7fc518 --- /dev/null +++ b/UVtools.ScriptSample/ScriptLightBleedCompensationSample.cs @@ -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; + } + } +} diff --git a/UVtools.WPF/Assets/Icons/lightbulb-solid-16x16.png b/UVtools.WPF/Assets/Icons/lightbulb-solid-16x16.png Binary files differnew file mode 100644 index 0000000..f182d01 --- /dev/null +++ b/UVtools.WPF/Assets/Icons/lightbulb-solid-16x16.png diff --git a/UVtools.WPF/Controls/Tools/ToolLightBleedCompensationControl.axaml b/UVtools.WPF/Controls/Tools/ToolLightBleedCompensationControl.axaml new file mode 100644 index 0000000..294927e --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolLightBleedCompensationControl.axaml @@ -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> diff --git a/UVtools.WPF/Controls/Tools/ToolLightBleedCompensationControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLightBleedCompensationControl.axaml.cs new file mode 100644 index 0000000..1cb0f32 --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolLightBleedCompensationControl.axaml.cs @@ -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); + } + } +} diff --git a/UVtools.WPF/Extensions/WindowExtensions.cs b/UVtools.WPF/Extensions/WindowExtensions.cs index e1bf8ad..247f71a 100644 --- a/UVtools.WPF/Extensions/WindowExtensions.cs +++ b/UVtools.WPF/Extensions/WindowExtensions.cs @@ -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); diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 14750fc..d146014 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -175,6 +175,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 { diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index e54e0d5..16f4847 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -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> diff --git a/build/CreateRelease.WPF.ps1 b/build/CreateRelease.WPF.ps1 index f15382f..9521bea 100644 --- a/build/CreateRelease.WPF.ps1 +++ b/build/CreateRelease.WPF.ps1 @@ -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" . } |