Files
UVtools/UVtools.Core/Operations/OperationLayerExportImage.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

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
}