/* * GNU AFFERO GENERAL PUBLIC LICENSE * Version 3, 19 November 2007 * Copyright (C) 2007 Free Software Foundation, Inc. * 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(""); tw.WriteLine("\t"); tw.WriteLine("\t\t"); tw.WriteLine("\t"); tw.WriteLine($"\t{slicedFileNameNoExt} #{layerIndex}"); tw.WriteLine($"\t"); tw.WriteLine($"\t"); foreach (var path in paths) { tw.WriteLine($"\t"); } /*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");*/ tw.WriteLine("\t"); tw.WriteLine(""); } 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 }