mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
a626cfdc72
- (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
313 lines
10 KiB
C#
313 lines
10 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.CvEnum;
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using UVtools.Core.Extensions;
|
|
using UVtools.Core.FileFormats;
|
|
|
|
namespace UVtools.Core.Operations;
|
|
|
|
|
|
public sealed class OperationLayerExportImage : Operation
|
|
{
|
|
#region Enums
|
|
|
|
public enum LayerExportImageTypes : byte
|
|
{
|
|
[Description("PNG: Portable Network Graphics")]
|
|
PNG,
|
|
[Description("JPG: Joint Photographic Experts Group")]
|
|
JPG,
|
|
[Description("JPEG: Joint Photographic Experts Group")]
|
|
JPEG,
|
|
[Description("JP2: Joint Photographic Experts Group (JPEG 2000)")]
|
|
JP2,
|
|
[Description("TIF: Tag Image File Format")]
|
|
TIF,
|
|
[Description("TIFF: Tag Image File Format")]
|
|
TIFF,
|
|
[Description("BMP: Bitmap")]
|
|
BMP,
|
|
[Description("PBM: Portable Bitmap")]
|
|
PBM,
|
|
[Description("PGM: Portable Greymap")]
|
|
PGM,
|
|
//[Description("PPM: Portable Pixmap")]
|
|
//PPM,
|
|
[Description("SR: Sun raster")]
|
|
SR,
|
|
[Description("RAS: Sun raster")]
|
|
RAS,
|
|
[Description("SVG: Scalable Vector Graphics")]
|
|
SVG
|
|
}
|
|
#endregion
|
|
|
|
#region Members
|
|
|
|
private string _outputFolder = null!;
|
|
private string _filename = "layer";
|
|
private LayerExportImageTypes _imageType = LayerExportImageTypes.PNG;
|
|
private RotateDirection _rotateDirection = RotateDirection.None;
|
|
private FlipDirection _flipDirection = FlipDirection.None;
|
|
private bool _padLayerIndex = true;
|
|
private bool _cropByRoi = true;
|
|
|
|
#endregion
|
|
|
|
#region Overrides
|
|
|
|
public override bool CanHaveProfiles => false;
|
|
|
|
public override string IconClass => "fa-solid fa-file-image";
|
|
public override string Title => "Export layers to images";
|
|
|
|
public override string Description =>
|
|
"Export a layer range to images.";
|
|
|
|
public override string ConfirmationText =>
|
|
$"export {_imageType} images from layers {LayerIndexStart} through {LayerIndexEnd}?";
|
|
|
|
public override string ProgressTitle =>
|
|
$"Exporting {_imageType} images from layers {LayerIndexStart} through {LayerIndexEnd}";
|
|
|
|
public override string ProgressAction => $"Exported {_imageType} images";
|
|
|
|
/*public override string ValidateInternally()
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
if (LayerRangeCount < 2)
|
|
{
|
|
sb.AppendLine("To generate a heat map at least two layers are required.");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}*/
|
|
|
|
public override string ToString()
|
|
{
|
|
var result = $"[Crop by ROI: {_cropByRoi}] [Pad index: {_padLayerIndex}]" +
|
|
LayerRangeString;
|
|
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public string OutputFolder
|
|
{
|
|
get => _outputFolder;
|
|
set => RaiseAndSetIfChanged(ref _outputFolder, value);
|
|
}
|
|
|
|
public string Filename
|
|
{
|
|
get => _filename;
|
|
set => RaiseAndSetIfChanged(ref _filename, value);
|
|
}
|
|
|
|
public LayerExportImageTypes ImageType
|
|
{
|
|
get => _imageType;
|
|
set => RaiseAndSetIfChanged(ref _imageType, value);
|
|
}
|
|
|
|
public RotateDirection RotateDirection
|
|
{
|
|
get => _rotateDirection;
|
|
set => RaiseAndSetIfChanged(ref _rotateDirection, value);
|
|
}
|
|
|
|
public FlipDirection FlipDirection
|
|
{
|
|
get => _flipDirection;
|
|
set => RaiseAndSetIfChanged(ref _flipDirection, value);
|
|
}
|
|
|
|
public bool PadLayerIndex
|
|
{
|
|
get => _padLayerIndex;
|
|
set => RaiseAndSetIfChanged(ref _padLayerIndex, value);
|
|
}
|
|
|
|
public bool CropByROI
|
|
{
|
|
get => _cropByRoi;
|
|
set => RaiseAndSetIfChanged(ref _cropByRoi, value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public OperationLayerExportImage()
|
|
{ }
|
|
|
|
public OperationLayerExportImage(FileFormat slicerFile) : base(slicerFile)
|
|
{
|
|
_flipDirection = SlicerFile.DisplayMirror;
|
|
}
|
|
|
|
public override void InitWithSlicerFile()
|
|
{
|
|
_outputFolder = Path.Combine(Path.GetDirectoryName(SlicerFile.FileFullPath) ?? string.Empty, FileFormat.GetFileNameStripExtensions(SlicerFile.FileFullPath) ?? string.Empty);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
protected override bool ExecuteInternally(OperationProgress progress)
|
|
{
|
|
if (!Directory.Exists(_outputFolder))
|
|
{
|
|
Directory.CreateDirectory(_outputFolder);
|
|
}
|
|
|
|
var slicedFileNameNoExt = SlicerFile.FilenameNoExt;
|
|
|
|
Parallel.For(LayerIndexStart, LayerIndexEnd+1, CoreSettings.GetParallelOptions(progress), layerIndex =>
|
|
{
|
|
progress.PauseIfRequested();
|
|
using var mat = SlicerFile[layerIndex].LayerMat;
|
|
var matRoi = mat;
|
|
if (_cropByRoi && HaveROI)
|
|
{
|
|
matRoi = GetRoiOrDefault(mat);
|
|
}
|
|
|
|
if (_flipDirection != FlipDirection.None)
|
|
{
|
|
CvInvoke.Flip(matRoi, matRoi, (FlipType)_flipDirection);
|
|
}
|
|
|
|
if (_rotateDirection != RotateDirection.None)
|
|
{
|
|
CvInvoke.Rotate(matRoi, matRoi, (RotateFlags)_rotateDirection);
|
|
}
|
|
|
|
var filename = SlicerFile[layerIndex].FormatFileName(_filename, _padLayerIndex ? SlicerFile.LayerDigits : byte.MinValue, IndexStartNumber.Zero, string.Empty);
|
|
var fileFullPath = Path.Combine(_outputFolder, $"{filename}.{_imageType.ToString().ToLower()}");
|
|
|
|
if (_imageType != LayerExportImageTypes.SVG)
|
|
{
|
|
matRoi.Save(fileFullPath);
|
|
}
|
|
else
|
|
{
|
|
// SVG
|
|
|
|
var paths = matRoi.GetSvgPath(ChainApproxMethod.ChainApproxTc89Kcos);
|
|
|
|
using TextWriter tw = new StreamWriter(fileFullPath);
|
|
tw.WriteLine("<!--");
|
|
tw.WriteLine($"# Generated by {About.Software} v{About.VersionStr} {About.SystemBits} @ {DateTime.UtcNow} #");
|
|
tw.WriteLine($"File: {SlicerFile.Filename}");
|
|
tw.WriteLine($"{SlicerFile[layerIndex].ToString().Replace(", ", "\n")}");
|
|
tw.WriteLine("-->");
|
|
tw.WriteLine("<svg " +
|
|
"xmlns=\"http://www.w3.org/2000/svg\" " +
|
|
//"xmlns:xlink=\"http://www.w3.org/1999/xlink\" " +
|
|
//"version=\"1.1\" " +
|
|
$"id=\"{slicedFileNameNoExt}_{filename}\" " +
|
|
$"data-name=\"{slicedFileNameNoExt}_{filename}\" " +
|
|
//"x=\"0\" " +
|
|
//"y=\"0\" " +
|
|
$"width=\"{matRoi.Width}\" " +
|
|
$"height=\"{matRoi.Height}\" " +
|
|
$"viewBox=\"0 0 {matRoi.Width} {matRoi.Height}\">");
|
|
tw.WriteLine("\t<defs>");
|
|
tw.WriteLine("\t\t<style>");
|
|
//tw.WriteLine("\t\tsvg { background-color: #000000; }");
|
|
tw.WriteLine("\t\t.background { fill: #000000; }");
|
|
//tw.WriteLine("\t\t.black { fill: #000000; fill-rule: evenodd; }");
|
|
//tw.WriteLine("\t\t.white { fill: #FFFFFF; fill-rule: evenodd; }");
|
|
tw.WriteLine("\t\tpath { fill: #FFFFFF; fill-rule: evenodd; }");
|
|
tw.WriteLine("\t\t</style>");
|
|
tw.WriteLine("\t</defs>");
|
|
tw.WriteLine($"\t<title>{slicedFileNameNoExt} #{layerIndex}</title>");
|
|
|
|
tw.WriteLine($"\t<g id=\"layer{layerIndex}\">");
|
|
tw.WriteLine($"\t<rect class=\"background\" width=\"{mat.Width}\" height=\"{mat.Height}\"/>");
|
|
|
|
foreach (var path in paths)
|
|
{
|
|
tw.WriteLine($"\t<path d=\"{path}\"/>");
|
|
}
|
|
|
|
/*bool firstTime = true;
|
|
for (int i = 0; i < contours.Size; i++)
|
|
{
|
|
if (hierarchy[i, EmguContour.HierarchyParent] == -1) // Top hierarchy
|
|
{
|
|
if (firstTime)
|
|
{
|
|
firstTime = false;
|
|
}
|
|
else
|
|
{
|
|
tw.WriteLine("\"/>");
|
|
}
|
|
|
|
tw.Write("\t<path d=\"");
|
|
}
|
|
else
|
|
{
|
|
tw.Write(" ");
|
|
}
|
|
|
|
tw.Write($"M {contours[i][0].X} {contours[i][0].Y} L");
|
|
for (int x = 1; x < contours[i].Size; x++)
|
|
{
|
|
tw.Write($" {contours[i][x].X} {contours[i][x].Y}");
|
|
}
|
|
tw.Write(" Z");
|
|
}
|
|
|
|
if(!firstTime) tw.WriteLine("\"/>");*/
|
|
|
|
|
|
tw.WriteLine("\t</g>");
|
|
tw.WriteLine("</svg>");
|
|
}
|
|
|
|
progress.LockAndIncrement();
|
|
});
|
|
|
|
return !progress.Token.IsCancellationRequested;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Equality
|
|
|
|
private bool Equals(OperationLayerExportImage other)
|
|
{
|
|
return _outputFolder == other._outputFolder && _filename == other._filename && _imageType == other._imageType && _rotateDirection == other._rotateDirection && _flipDirection == other._flipDirection && _padLayerIndex == other._padLayerIndex && _cropByRoi == other._cropByRoi;
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return ReferenceEquals(this, obj) || obj is OperationLayerExportImage other && Equals(other);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return HashCode.Combine(_outputFolder, _filename, (int) _imageType, (int) _rotateDirection, (int) _flipDirection, _padLayerIndex, _cropByRoi);
|
|
}
|
|
|
|
#endregion
|
|
} |