Files
UVtools/UVtools.Core/Operations/OperationLightBleedCompensation.cs
Tiago Conceição a626cfdc72 v3.12.0
- (Add) Allow to pause and resume operations (#654)
- (Add) `Layer.FirstTransitionLayer`
- (Add) `Layer.LastTransitionLayer`
- (Add) File format: Elegoo GOO
- (Add) PrusaSlicer Printer: Elegoo Mars 4
- (Improvement) Allocate maximum GPU memory for Skia up to 256 MB
- (Improvement) Set and sanitize transition layers exposure time from last bottom layer and first normal layer instead of global times (#659)
- (Change) CXDLP: Default version from 2 to 3
- (Fix) UI was not rendering with GPU (ANGLE)
- (Fix) `Layer.IsTransitionLayer` was returning the wrong value
- (Upgrade) .NET from 6.0.13 to 6.0.14
2023-02-27 03:22:40 +00:00

275 lines
8.6 KiB
C#

/*
* 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 Emgu.CV;
using Emgu.CV.Structure;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
namespace UVtools.Core.Operations;
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 LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.Normal;
public override string IconClass => "mdi-lightbulb-on";
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, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
progress.PauseIfRequested();
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
}