Files
UVtools/UVtools.Core/Operations/OperationPixelArithmetic.cs
T
Tiago Conceição d90f7e6f75 v2.13.1
- (Add) Layer preview - Outline: Skeletonize
- (Add) Actions - Export layers to skeleton: Export a layer range to a skeletonized image that is the sum of each layer skeleton
- (Add) Pixel editor - Text: Allow to rotate text placement by any angle (#206)
- (Add) Calibrate - XYZ Accuracy: Drain hole diameter (#205)
2021-05-29 04:14:03 +01:00

363 lines
13 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 System;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
namespace UVtools.Core.Operations
{
[Serializable]
public class OperationPixelArithmetic : Operation
{
#region Members
private PixelArithmeticOperators _operator = PixelArithmeticOperators.Set;
private byte _value = byte.MaxValue;
private ThresholdType _thresholdType = ThresholdType.Binary;
private byte _thresholdMaxValue = 255;
private bool _affectBackPixels;
#endregion
#region Enums
public enum PixelArithmeticOperators : byte
{
[Description("Set: to a brightness")]
Set,
[Description("Add: with a brightness")]
Add,
[Description("Subtract: with a brightness")]
Subtract,
[Description("Multiply: with a brightness")]
Multiply,
[Description("Divide: with a brightness")]
Divide,
//[Description("Exponential: pixels by a brightness")]
//Exponential,
[Description("Minimum: set to a brightness if is lower than the current pixel")]
Minimum,
[Description("Maximum: set to a brightness if is higher than the current pixel")]
Maximum,
[Description("Bitwise Not: invert pixels")]
BitwiseNot,
[Description("Bitwise And: with a brightness")]
BitwiseAnd,
[Description("Bitwise Or: with a brightness")]
BitwiseOr,
[Description("Bitwise Xor: with a brightness")]
BitwiseXor,
[Description("AbsDiff: perform a absolute difference between pixel and brightness")]
AbsDiff,
[Description("Threshold: between a minimum/maximum brightness")]
Threshold,
[Description("Keep Region: in the selected ROI or masks")]
KeepRegion,
[Description("Discard Region: in the selected ROI or masks")]
DiscardRegion
}
#endregion
#region Overrides
public override string Title => "Pixel arithmetic";
public override string Description =>
"Perform arithmetic operations over the pixels.";
public override string ConfirmationText =>
$"arithmetic {_operator}" +
(ValueEnabled ? $"={_value}" : string.Empty) +
(_operator is PixelArithmeticOperators.Threshold ? $"/{_thresholdMaxValue}" : string.Empty)
+ $" layers from {LayerIndexStart} through {LayerIndexEnd}";
public override string ProgressTitle =>
$"Arithmetic {_operator}"+
(ValueEnabled ? $"={_value}" : string.Empty)
+$" layers from {LayerIndexStart} through {LayerIndexEnd}";
public override string ProgressAction => "Calculated layers";
public override string ValidateInternally()
{
var sb = new StringBuilder();
if (_operator == PixelArithmeticOperators.KeepRegion && !HaveROI && !HaveMask)
{
sb.AppendLine("The 'Keep' operator requires selected ROI/masks.");
}
else if (_operator == PixelArithmeticOperators.DiscardRegion && !HaveROI && !HaveMask)
{
sb.AppendLine("The 'Discard' operator requires selected ROI/masks.");
}
else if (_operator
is PixelArithmeticOperators.Add
or PixelArithmeticOperators.Subtract
or PixelArithmeticOperators.Maximum
or PixelArithmeticOperators.BitwiseOr
or PixelArithmeticOperators.BitwiseXor
or PixelArithmeticOperators.AbsDiff
&& _value == 0)
/*||
(_operator is PixelArithmeticOperators.Exponential && _value == 1)
)*/
{
sb.AppendLine($"{_operator} by {_value} will have no effect.");
}
else if (_operator == PixelArithmeticOperators.Divide && _value == 0)
{
sb.AppendLine("Can't divide by 0.");
}
return sb.ToString();
}
public override string ToString()
{
var result = $"[{_operator}: {_value}] [ABP: {_affectBackPixels}]"
+ LayerRangeString;
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
#endregion
#region Properties
public PixelArithmeticOperators Operator
{
get => _operator;
set
{
if(!RaiseAndSetIfChanged(ref _operator, value)) return;
RaisePropertyChanged(nameof(ValueEnabled));
RaisePropertyChanged(nameof(ThresholdEnabled));
RaisePropertyChanged(nameof(AffectBackPixelsEnabled));
}
}
public byte Value
{
get => _value;
set
{
if(!RaiseAndSetIfChanged(ref _value, value)) return;
RaisePropertyChanged(nameof(ValuePercent));
}
}
// 255 - 100
//value - x
public float ValuePercent => (float) Math.Round(_value * 100f / byte.MaxValue, 2);
public bool ValueEnabled => _operator
is not PixelArithmeticOperators.BitwiseNot
and not PixelArithmeticOperators.KeepRegion
and not PixelArithmeticOperators.DiscardRegion
;
public ThresholdType ThresholdType
{
get => _thresholdType;
set => RaiseAndSetIfChanged(ref _thresholdType, value);
}
public byte ThresholdMaxValue
{
get => _thresholdMaxValue;
set => RaiseAndSetIfChanged(ref _thresholdMaxValue, value);
}
public bool ThresholdEnabled => _operator is PixelArithmeticOperators.Threshold;
public bool AffectBackPixels
{
get => _affectBackPixels;
set => RaiseAndSetIfChanged(ref _affectBackPixels, value);
}
public bool AffectBackPixelsEnabled => _operator
is not PixelArithmeticOperators.Subtract
and not PixelArithmeticOperators.Multiply
and not PixelArithmeticOperators.Divide
and not PixelArithmeticOperators.BitwiseNot
and not PixelArithmeticOperators.BitwiseAnd
and not PixelArithmeticOperators.KeepRegion
and not PixelArithmeticOperators.DiscardRegion
and not PixelArithmeticOperators.Threshold
;
#endregion
#region Constructor
public OperationPixelArithmetic() { }
public OperationPixelArithmetic(FileFormat slicerFile) : base(slicerFile) { }
#endregion
#region Methods
protected override bool ExecuteInternally(OperationProgress progress)
{
var tempMat = GetTempMat();
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex =>
{
if (progress.Token.IsCancellationRequested) return;
using (var mat = SlicerFile[layerIndex].LayerMat)
{
Execute(mat, tempMat);
SlicerFile[layerIndex].LayerMat = mat;
}
progress.LockAndIncrement();
});
tempMat?.Dispose();
return !progress.Token.IsCancellationRequested;
}
public override bool Execute(Mat mat, params object[] arguments)
{
using var original = mat.Clone();
var target = GetRoiOrDefault(mat);
Mat tempMat;
bool needDispose = false;
if (arguments is not null && arguments.Length > 0)
{
tempMat = arguments[0] as Mat;
}
else
{
tempMat = GetTempMat();
needDispose = true;
}
switch (_operator)
{
case PixelArithmeticOperators.Set:
tempMat.CopyTo(target, _affectBackPixels ? null : target);
break;
case PixelArithmeticOperators.Add:
CvInvoke.Add(target, tempMat, target, _affectBackPixels ? null : target);
break;
case PixelArithmeticOperators.Subtract:
CvInvoke.Subtract(target, tempMat, target, _affectBackPixels ? null : target);
break;
case PixelArithmeticOperators.Multiply:
CvInvoke.Multiply(target, tempMat, target);
break;
case PixelArithmeticOperators.Divide:
CvInvoke.Divide(target, tempMat, target);
break;
/*case PixelArithmeticOperators.Exponential:
CvInvoke.Pow(target, _value, tempMat);
if(!_affectBackPixels) ApplyMask(original, mat, original);
break;*/
case PixelArithmeticOperators.Minimum:
CvInvoke.Min(target, tempMat, target);
if (!_affectBackPixels) ApplyMask(original, target, original);
break;
case PixelArithmeticOperators.Maximum:
CvInvoke.Max(target, tempMat, target);
if (!_affectBackPixels) ApplyMask(original, target, original);
break;
case PixelArithmeticOperators.BitwiseNot:
CvInvoke.BitwiseNot(target, target);
break;
case PixelArithmeticOperators.BitwiseAnd:
CvInvoke.BitwiseAnd(target, tempMat, target);
break;
case PixelArithmeticOperators.BitwiseOr:
CvInvoke.BitwiseOr(target, tempMat, target, _affectBackPixels ? null : target);
break;
case PixelArithmeticOperators.BitwiseXor:
CvInvoke.BitwiseXor(target, tempMat, target, _affectBackPixels ? null : target);
break;
case PixelArithmeticOperators.Threshold:
CvInvoke.Threshold(target, target, _value, _thresholdMaxValue, _thresholdType);
break;
case PixelArithmeticOperators.AbsDiff:
CvInvoke.AbsDiff(target, tempMat, target);
if (!_affectBackPixels) ApplyMask(original, target, original);
break;
case PixelArithmeticOperators.KeepRegion:
{
using var targetClone = target.Clone();
original.SetTo(EmguExtensions.BlackColor);
mat.SetTo(EmguExtensions.BlackColor);
targetClone.CopyTo(target);
break;
}
case PixelArithmeticOperators.DiscardRegion:
target.SetTo(EmguExtensions.BlackColor);
break;
default:
throw new NotImplementedException();
}
ApplyMask(original, target);
if (needDispose)
{
tempMat?.Dispose();
}
return true;
}
public Mat GetTempMat() => _operator
is not PixelArithmeticOperators.BitwiseNot
and not PixelArithmeticOperators.KeepRegion
and not PixelArithmeticOperators.DiscardRegion ? EmguExtensions.InitMat(HaveROI ? ROI.Size : SlicerFile.Resolution, new MCvScalar(_value)) : null;
public void PresetStripAntiAliasing()
{
Operator = PixelArithmeticOperators.Threshold;
Value = 127;
ThresholdMaxValue = 255;
ThresholdType = ThresholdType.Binary;
}
public void PresetHalfBrightness()
{
Value = 128;
}
#endregion
#region Equality
protected bool Equals(OperationPixelArithmetic other)
{
return _operator == other._operator && _value == other._value && _thresholdType == other._thresholdType && _thresholdMaxValue == other._thresholdMaxValue && _affectBackPixels == other._affectBackPixels;
}
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((OperationPixelArithmetic) obj);
}
public override int GetHashCode()
{
return HashCode.Combine((int) _operator, _value, (int) _thresholdType, _thresholdMaxValue, _affectBackPixels);
}
#endregion
}
}