Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'UVtools.Core/FileFormats/OSLAFile.cs')
-rw-r--r--UVtools.Core/FileFormats/OSLAFile.cs800
1 files changed, 800 insertions, 0 deletions
diff --git a/UVtools.Core/FileFormats/OSLAFile.cs b/UVtools.Core/FileFormats/OSLAFile.cs
new file mode 100644
index 0000000..94d956d
--- /dev/null
+++ b/UVtools.Core/FileFormats/OSLAFile.cs
@@ -0,0 +1,800 @@
+/*
+ * 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.
+ */
+
+// https://github.com/sn4k3/UVtools/blob/master/Documentation/osla.md
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using BinarySerialization;
+using Emgu.CV;
+using Emgu.CV.CvEnum;
+using MoreLinq;
+using UVtools.Core.Extensions;
+using UVtools.Core.GCode;
+using UVtools.Core.Operations;
+
+namespace UVtools.Core.FileFormats
+{
+ public class OSLAFile : FileFormat
+ {
+ #region Constants
+
+ public const string MARKER = "OSLATiCo";
+ #endregion
+
+ #region Sub Classes
+ #region Header
+ public class FileDef
+ {
+ [FieldOrder(0)]
+ [FieldLength(8)]
+ public string Marker { get; set; } = MARKER;
+
+ [FieldOrder(1)]
+ public ushort Version { get; set; } = 0;
+
+ [FieldOrder(2)]
+ [FieldLength(20)]
+ public string CreatedDateTime { get; set; } = DateTime.UtcNow.ToString("u");
+
+ [FieldOrder(3)]
+ [FieldLength(50)]
+ [SerializeAs(SerializedType.TerminatedString)]
+ public string CreatedBy { get; set; } = About.SoftwareWithVersion;
+
+ [FieldOrder(4)]
+ [FieldLength(20)]
+ public string ModifiedDateTime { get; set; } = DateTime.UtcNow.ToString("u");
+
+ [FieldOrder(5)]
+ [FieldLength(50)]
+ [SerializeAs(SerializedType.TerminatedString)]
+ public string ModifiedBy { get; set; } = About.SoftwareWithVersion;
+
+ public override string ToString()
+ {
+ return $"{nameof(Marker)}: {Marker}, {nameof(Version)}: {Version}, {nameof(CreatedDateTime)}: {CreatedDateTime}, {nameof(CreatedBy)}: {CreatedBy}, {nameof(ModifiedDateTime)}: {ModifiedDateTime}, {nameof(ModifiedBy)}: {ModifiedBy}";
+ }
+
+ public void Update()
+ {
+ ModifiedDateTime= DateTime.UtcNow.ToString("u");
+ ModifiedBy = About.SoftwareWithVersion;;
+ }
+
+ public void Validate()
+ {
+ if (Marker != MARKER)
+ {
+ throw new FileLoadException($"Invalid marker: {Marker}, not a valid OSLA file.");
+ }
+ }
+ }
+
+
+ public class Header
+ {
+ [FieldOrder(0)] public uint TableSize { get; set; }
+ [FieldOrder(1)] public uint ResolutionX { get; set; }
+ [FieldOrder(2)] public uint ResolutionY { get; set; }
+ [FieldOrder(3)] public float MachineZ { get; set; }
+ [FieldOrder(4)] public float DisplayWidth { get; set; }
+ [FieldOrder(5)] public float DisplayHeight { get; set; }
+ [FieldOrder(6)] public byte DisplayMirror { get; set; } // 0 = No mirror | 1 = Horizontally | 2 = Vertically | 3 = Horizontally+Vertically | >3 = No mirror
+ [FieldOrder(7)] [FieldLength(16)] [SerializeAs(SerializedType.TerminatedString)] public string PreviewDataType { get; set; } = "RGB565";
+ [FieldOrder(8)] [FieldLength(16)] [SerializeAs(SerializedType.TerminatedString)] public string LayerDataType { get; set; } = "PNG";
+ [FieldOrder(9)] public uint PreviewTableSize { get; set; } = 8;
+ [FieldOrder(10)] public uint PreviewCount { get; set; }
+ [FieldOrder(11)] public float LayerHeight { get; set; } = 0.05f;
+ [FieldOrder(12)] public ushort BottomLayersCount { get; set; } = 4;
+ [FieldOrder(13)] public uint LayerCount { get; set; }
+ [FieldOrder(14)] public uint LayerTableSize { get; set; } = 4;
+ [FieldOrder(15)] public uint LayerDefinitionsAddress { get; set; }
+ [FieldOrder(16)] public uint GCodeAddress { get; set; }
+ [FieldOrder(17)] public uint PrintTime { get; set; }
+ [FieldOrder(18)] public float MaterialMilliliters { get; set; }
+ [FieldOrder(19)] public float MaterialCost { get; set; }
+ [FieldOrder(20)] [FieldLength(50)] [SerializeAs(SerializedType.TerminatedString)] public string MaterialName { get; set; }
+ [FieldOrder(21)] [FieldLength(50)] [SerializeAs(SerializedType.TerminatedString)] public string MachineName { get; set; } = "Unknown";
+
+ public override string ToString()
+ {
+ return $"{nameof(TableSize)}: {TableSize}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(MachineZ)}: {MachineZ}, {nameof(DisplayWidth)}: {DisplayWidth}, {nameof(DisplayHeight)}: {DisplayHeight}, {nameof(DisplayMirror)}: {DisplayMirror}, {nameof(PreviewDataType)}: {PreviewDataType}, {nameof(LayerDataType)}: {LayerDataType}, {nameof(PreviewTableSize)}: {PreviewTableSize}, {nameof(PreviewCount)}: {PreviewCount}, {nameof(LayerTableSize)}: {LayerTableSize}, {nameof(BottomLayersCount)}: {BottomLayersCount}, {nameof(LayerCount)}: {LayerCount}, {nameof(LayerDefinitionsAddress)}: {LayerDefinitionsAddress}, {nameof(GCodeAddress)}: {GCodeAddress}, {nameof(PrintTime)}: {PrintTime}, {nameof(MaterialMilliliters)}: {MaterialMilliliters}, {nameof(MaterialCost)}: {MaterialCost}, {nameof(MaterialName)}: {MaterialName}, {nameof(MachineName)}: {MachineName}";
+ }
+ }
+ #endregion
+
+ #region Custom Table
+
+ public class CustomTable
+ {
+ [FieldOrder(0)] public uint TableSize { get; set; }
+
+ [FieldOrder(1)] [FieldCount(nameof(TableSize))] public byte[] Bytes { get; set; } = Array.Empty<byte>();
+
+ public override string ToString()
+ {
+ return $"{nameof(TableSize)}: {TableSize}, {nameof(Bytes)}: {Bytes.Length}";
+ }
+ }
+
+ #endregion
+
+ #region Preview
+ public class Preview
+ {
+ /// <summary>
+ /// Gets the X dimension of the preview image, in pixels.
+ /// </summary>
+ [FieldOrder(0)] public ushort ResolutionX { get; set; }
+
+ /// <summary>
+ /// Gets the Y dimension of the preview image, in pixels.
+ /// </summary>
+ [FieldOrder(1)] public ushort ResolutionY { get; set; }
+
+ /// <summary>
+ /// Gets the image length in bytes.
+ /// </summary>
+ [FieldOrder(2)] public uint ImageLength { get; set; }
+ //[FieldOrder(3)] [FieldCount(nameof(ImageLength))] public byte[] ImageData { get; set; }
+
+
+ public override string ToString()
+ {
+ return $"{nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(ImageLength)}: {ImageLength}";
+ }
+ }
+
+ #endregion
+
+ #region Layer
+ public class LayerDef
+ {
+ [FieldOrder(0)] public uint DataAddress { get; set; }
+
+ [Ignore] public byte[] ImageData { get; set; }
+
+ public LayerDef()
+ {
+ }
+
+ public override string ToString()
+ {
+ return $"{nameof(DataAddress)}: {DataAddress}, {nameof(ImageData)}: {ImageData.Length}";
+ }
+ }
+ #endregion
+
+ #region GCode
+
+ public class GCodeDef
+ {
+ [FieldOrder(0)]
+ public uint GCodeSize { get; set; }
+
+ [FieldOrder(1)] [FieldLength(nameof(GCodeSize))]
+ public string GCodeText { get; set; }
+ }
+ #endregion
+
+ #endregion
+
+ #region Properties
+
+ public FileDef FileSettings { get; protected internal set; } = new();
+ public Header HeaderSettings { get; protected internal set; } = new();
+ public CustomTable CustomTableSettings { get; protected internal set; } = new();
+ public Preview[] Previews { get; protected internal set; }
+
+ public override FileFormatType FileType => FileFormatType.Binary;
+
+ public override FileExtension[] FileExtensions { get; } = {
+ new ("osla", "Open SLA universal binary file"),
+ //new ("omsla", "Open mSLA universal binary file"),
+ //new ("odlp", "Open DLP universal binary file"),
+ };
+
+ public override PrintParameterModifier[] PrintParameterModifiers { get; } =
+ {
+ PrintParameterModifier.BottomLayerCount,
+
+ PrintParameterModifier.BottomWaitTimeBeforeCure,
+ PrintParameterModifier.WaitTimeBeforeCure,
+
+ PrintParameterModifier.BottomExposureTime,
+ PrintParameterModifier.ExposureTime,
+
+ PrintParameterModifier.BottomWaitTimeAfterCure,
+ PrintParameterModifier.WaitTimeAfterCure,
+
+ PrintParameterModifier.BottomLiftHeight,
+ PrintParameterModifier.BottomLiftSpeed,
+ PrintParameterModifier.LiftHeight,
+ PrintParameterModifier.LiftSpeed,
+
+ PrintParameterModifier.BottomWaitTimeAfterLift,
+ PrintParameterModifier.WaitTimeAfterLift,
+
+ PrintParameterModifier.RetractSpeed,
+
+
+ PrintParameterModifier.BottomLightPWM,
+ PrintParameterModifier.LightPWM,
+ };
+
+ public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.WaitTimeBeforeCure,
+ PrintParameterModifier.ExposureTime,
+ PrintParameterModifier.WaitTimeAfterCure,
+ PrintParameterModifier.LiftHeight,
+ PrintParameterModifier.LiftSpeed,
+ PrintParameterModifier.WaitTimeAfterLift,
+ PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.LightPWM,
+ };
+
+ public override Size[] ThumbnailsOriginalSize { get; } =
+ {
+ new(400, 400),
+ new(200, 200)
+ };
+
+ public override uint ResolutionX
+ {
+ get => HeaderSettings.ResolutionX;
+ set
+ {
+ HeaderSettings.ResolutionX = value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override uint ResolutionY
+ {
+ get => HeaderSettings.ResolutionY;
+ set
+ {
+ HeaderSettings.ResolutionY = value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float DisplayWidth
+ {
+ get => HeaderSettings.DisplayWidth;
+ set
+ {
+ HeaderSettings.DisplayWidth = (float)Math.Round(value, 2);
+ RaisePropertyChanged();
+ }
+ }
+
+
+ public override float DisplayHeight
+ {
+ get => HeaderSettings.DisplayHeight;
+ set
+ {
+ HeaderSettings.DisplayHeight = (float)Math.Round(value, 2);
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float MachineZ
+ {
+ get => HeaderSettings.MachineZ > 0 ? HeaderSettings.MachineZ : base.MachineZ;
+ set
+ {
+ HeaderSettings.MachineZ = (float)Math.Round(value, 2);
+ RaisePropertyChanged();
+ }
+ }
+
+ public override bool DisplayMirror
+ {
+ get => HeaderSettings.DisplayMirror is >= 1 and <= 3;
+ set
+ {
+ HeaderSettings.DisplayMirror = value ? (byte)1 : (byte)0;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override byte AntiAliasing
+ {
+ get => 8;
+ set => RaisePropertyChanged();
+ }
+
+ public override float LayerHeight
+ {
+ get => HeaderSettings.LayerHeight;
+ set
+ {
+ HeaderSettings.LayerHeight = Layer.RoundHeight(value);
+ RaisePropertyChanged();
+ }
+ }
+
+ public override uint LayerCount
+ {
+ get => base.LayerCount;
+ set => HeaderSettings.LayerCount = base.LayerCount;
+ }
+
+ public override ushort BottomLayerCount
+ {
+ get => HeaderSettings.BottomLayersCount;
+ set => base.BottomLayerCount = HeaderSettings.BottomLayersCount = value;
+ }
+
+ public override float PrintTime
+ {
+ get => base.PrintTime;
+ set
+ {
+ base.PrintTime = value;
+ HeaderSettings.PrintTime = (uint)base.PrintTime;
+ }
+ }
+
+ public override float MaterialMilliliters
+ {
+ get => base.MaterialMilliliters;
+ set
+ {
+ base.MaterialMilliliters = value;
+ HeaderSettings.MaterialMilliliters = base.MaterialMilliliters;
+ }
+ }
+
+ public override float MaterialCost
+ {
+ get => (float) Math.Round(HeaderSettings.MaterialCost, 3);
+ set => base.MaterialCost = HeaderSettings.MaterialCost = (float)Math.Round(value, 3);
+ }
+
+ public override string MaterialName
+ {
+ get => HeaderSettings.MaterialName;
+ set => base.MaterialName = HeaderSettings.MaterialName = value;
+ }
+
+ public override string MachineName
+ {
+ get => HeaderSettings.MachineName;
+ set => base.MachineName = HeaderSettings.MachineName = value;
+ }
+
+ public override object[] Configs => new object[] { FileSettings, HeaderSettings };
+
+ #endregion
+
+ #region Constructors
+ public OSLAFile()
+ {
+ //Previews = new Preview[ThumbnailsCount];
+ GCode = new GCodeBuilder
+ {
+ UseTailComma = true,
+ UseComments = true,
+ GCodePositioningType = GCodeBuilder.GCodePositioningTypes.Absolute,
+ GCodeSpeedUnit = GCodeBuilder.GCodeSpeedUnits.MillimetersPerMinute,
+ GCodeTimeUnit = GCodeBuilder.GCodeTimeUnits.Milliseconds,
+ GCodeShowImageType = GCodeBuilder.GCodeShowImageTypes.LayerIndex0Started,
+ LayerMoveCommand = GCodeBuilder.GCodeMoveCommands.G0,
+ EndGCodeMoveCommand = GCodeBuilder.GCodeMoveCommands.G0,
+ CommandShowImageM6054 = {Arguments = "{0}"},
+ };
+ }
+ #endregion
+
+ #region Methods
+ public override void Clear()
+ {
+ base.Clear();
+
+ Previews = null;
+ }
+
+ protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
+ {
+ using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write);
+ FileSettings.Update();
+ var fileDefSize = Helpers.SerializeWriteFileStream(outputFile, FileSettings);
+ HeaderSettings.TableSize = (uint)Helpers.Serializer.SizeOf(HeaderSettings);
+
+ outputFile.Seek((int)HeaderSettings.TableSize, SeekOrigin.Current);
+
+ Helpers.SerializeWriteFileStream(outputFile, CustomTableSettings); // Custom table
+
+ // Previews
+ progress.Reset(OperationProgress.StatusEncodePreviews, ThumbnailsCount);
+ HeaderSettings.PreviewCount = 0;
+ uint sizeofPreview = 0;
+ for (byte i = 0; i < ThumbnailsCount; i++)
+ {
+ var image = Thumbnails[i];
+ if(image is null) continue;
+
+ progress.Token.ThrowIfCancellationRequested();
+
+ var bytes = EncodeImage(image, HeaderSettings.PreviewDataType);
+ if (bytes.Length == 0) continue;
+ var preview = new Preview
+ {
+ ResolutionX = (ushort) image.Width,
+ ResolutionY = (ushort) image.Height,
+ ImageLength = (uint) bytes.Length,
+ };
+
+ if (sizeofPreview == 0)
+ {
+ sizeofPreview = (uint) Helpers.Serializer.SizeOf(preview);
+ }
+
+ HeaderSettings.PreviewCount++;
+
+ Helpers.SerializeWriteFileStream(outputFile, preview);
+ // Need to fill what we don't know
+ if (HeaderSettings.PreviewTableSize > sizeofPreview)
+ {
+ outputFile.Seek(HeaderSettings.LayerTableSize - sizeofPreview, SeekOrigin.Current);
+ }
+ outputFile.WriteBytes(bytes);
+
+ progress++;
+ }
+
+ uint[] layerDataAddresses = new uint[LayerCount];
+ progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
+ HeaderSettings.LayerDefinitionsAddress = (uint) outputFile.Position;
+
+ outputFile.Seek(HeaderSettings.LayerTableSize * LayerCount, SeekOrigin.Current); // Start of layer data
+
+ var layerHash = new Dictionary<string, uint>();
+
+ var range = Enumerable.Range(0, (int)LayerCount);
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
+ {
+ var layerBytes = new byte[LayerCount][];
+
+ Parallel.ForEach(batch, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ using var mat = this[layerIndex].LayerMat;
+ layerBytes[layerIndex] = EncodeImage(mat, HeaderSettings.LayerDataType);
+ progress.LockAndIncrement();
+ });
+
+ foreach (var layerIndex in batch)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+
+ // Try to reuse layers
+ var hash = Helpers.ComputeSHA1Hash(layerBytes[layerIndex]);
+ if (layerHash.TryGetValue(hash, out var address))
+ {
+ layerDataAddresses[layerIndex] = address;
+ }
+ else
+ {
+ layerDataAddresses[layerIndex] = (uint)outputFile.Position;
+ outputFile.WriteUIntLittleEndian((uint)layerBytes[layerIndex].Length);
+ outputFile.WriteBytes(layerBytes[layerIndex]);
+ layerHash.Add(hash, layerDataAddresses[layerIndex]);
+ }
+
+
+
+ layerBytes[layerIndex] = null; // Clean
+ }
+ }
+
+ HeaderSettings.GCodeAddress = (uint)outputFile.Position;
+
+ outputFile.Seek(HeaderSettings.LayerDefinitionsAddress, SeekOrigin.Begin);
+ for (int i = 0; i < layerDataAddresses.Length; i++)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+ outputFile.WriteUIntLittleEndian(layerDataAddresses[i]);
+ // Need to fill what we don't know
+ if (HeaderSettings.LayerTableSize > 4)
+ {
+ outputFile.Seek(HeaderSettings.LayerTableSize - 4, SeekOrigin.Current);
+ }
+ }
+
+ progress.Reset(OperationProgress.StatusEncodeGcode);
+ outputFile.Seek(HeaderSettings.GCodeAddress, SeekOrigin.Begin);
+ RebuildGCode();
+ var gcodeSettings = new GCodeDef { GCodeText = GCodeStr };
+ gcodeSettings.GCodeSize = (uint)gcodeSettings.GCodeText.Length;
+ Helpers.SerializeWriteFileStream(outputFile, gcodeSettings);
+
+ outputFile.Seek(fileDefSize, SeekOrigin.Begin);
+ Helpers.SerializeWriteFileStream(outputFile, HeaderSettings);
+
+ Debug.WriteLine("Encode Results:");
+ Debug.WriteLine(FileSettings);
+ Debug.WriteLine(HeaderSettings);
+ Debug.WriteLine("-End-");
+ }
+
+ protected override void DecodeInternally(string fileFullPath, OperationProgress progress)
+ {
+ using var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read);
+ FileSettings = Helpers.Deserialize<FileDef>(inputFile);
+ Debug.Write("File -> ");
+ Debug.WriteLine(FileSettings);
+ FileSettings.Validate();
+
+ HeaderSettings = Helpers.Deserialize<Header>(inputFile);
+ Debug.Write("Header -> ");
+ Debug.WriteLine(HeaderSettings);
+
+ var headerSize = Helpers.Serializer.SizeOf(HeaderSettings);
+ if (HeaderSettings.TableSize > headerSize) // By pass what we dont know
+ {
+ inputFile.Seek(HeaderSettings.TableSize - headerSize, SeekOrigin.Current);
+ }
+
+ CustomTableSettings = Helpers.Deserialize<CustomTable>(inputFile);
+ Debug.Write("Custom table -> ");
+ Debug.WriteLine(CustomTableSettings);
+
+ progress.Reset(OperationProgress.StatusDecodePreviews, ThumbnailsCount);
+
+ Previews = new Preview[HeaderSettings.PreviewCount];
+ for (byte i = 0; i < HeaderSettings.PreviewCount; i++)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+ Previews[i] = Helpers.Deserialize<Preview>(inputFile);
+
+ Debug.Write($"Preview {i} -> ");
+ Debug.WriteLine(Previews[i]);
+
+ // Need to fill what we don't know
+ if (HeaderSettings.PreviewTableSize > 8)
+ {
+ inputFile.Seek(HeaderSettings.LayerTableSize - 8, SeekOrigin.Current);
+ }
+
+
+ var bytes = inputFile.ReadBytes((int)Previews[i].ImageLength);
+
+ Thumbnails[i] = DecodeImage(bytes, HeaderSettings.PreviewDataType, Previews[i].ResolutionX, Previews[i].ResolutionY);
+ progress++;
+ }
+
+ inputFile.Seek(HeaderSettings.LayerDefinitionsAddress, SeekOrigin.Begin);
+
+ LayerManager.Init(HeaderSettings.LayerCount);
+ progress.Reset(OperationProgress.StatusGatherLayers, HeaderSettings.LayerCount);
+ uint[] layerDataAddresses = new uint[LayerCount];
+ for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+ layerDataAddresses[layerIndex] = inputFile.ReadUIntLittleEndian();
+ if (HeaderSettings.LayerTableSize > 4)
+ {
+ inputFile.Seek(HeaderSettings.LayerTableSize - 4, SeekOrigin.Current);
+ }
+
+ progress++;
+ }
+
+
+
+ progress.Reset(OperationProgress.StatusDecodeLayers, HeaderSettings.LayerCount);
+ var range = Enumerable.Range(0, (int)LayerCount);
+ foreach (var batch in MoreEnumerable.Batch(range, Environment.ProcessorCount * 10))
+ {
+ var layerBytes = new byte[LayerCount][];
+
+ foreach (var layerIndex in batch)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+
+ inputFile.Seek(layerDataAddresses[layerIndex], SeekOrigin.Begin);
+ layerBytes[layerIndex] = inputFile.ReadBytes(inputFile.ReadUIntLittleEndian());
+ }
+
+ Parallel.ForEach(batch, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ using var mat = DecodeImage(layerBytes[layerIndex], HeaderSettings.LayerDataType, Resolution);
+ this[layerIndex] = new Layer((uint)layerIndex, mat, this);
+ layerBytes[layerIndex] = null; // Clean
+
+ progress.LockAndIncrement();
+ });
+ }
+
+ progress.Reset(OperationProgress.StatusDecodeGcode);
+ inputFile.Seek(HeaderSettings.GCodeAddress, SeekOrigin.Begin);
+ var gcodeDef = Helpers.Deserialize<GCodeDef>(inputFile);
+ GCodeStr = gcodeDef.GCodeText;
+ GCode.ParseLayersFromGCode(this);
+ }
+
+ public override void SaveAs(string filePath = null, OperationProgress progress = null)
+ {
+ if (RequireFullEncode)
+ {
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ FileFullPath = filePath;
+ }
+ Encode(FileFullPath, progress);
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ File.Copy(FileFullPath, filePath, true);
+ FileFullPath = filePath;
+ }
+
+ using var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write);
+ outputFile.Seek(0, SeekOrigin.Begin);
+ FileSettings.Update();
+ Helpers.SerializeWriteFileStream(outputFile, FileSettings);
+ Helpers.SerializeWriteFileStream(outputFile, HeaderSettings);
+
+ outputFile.Seek(HeaderSettings.GCodeAddress, SeekOrigin.Begin);
+ outputFile.SetLength(HeaderSettings.GCodeAddress);
+
+ RebuildGCode();
+ var gcodeSettings = new GCodeDef {GCodeText = GCodeStr};
+ gcodeSettings.GCodeSize = (uint) gcodeSettings.GCodeText.Length;
+ Helpers.SerializeWriteFileStream(outputFile, gcodeSettings);
+ }
+ #endregion
+
+ #region Static Methods
+
+ public static byte[] EncodeImage(Mat mat, string encodeTo)
+ {
+ encodeTo = encodeTo.ToUpperInvariant();
+ if (encodeTo is "PNG" or "JPG" or "JPEG" or "JP2" or "BMP" or "TIF" or "TIFF" or "PPM" or "PMG" or "SR" or "RAS")
+ {
+ return CvInvoke.Imencode($".{encodeTo.ToLowerInvariant()}", mat);
+ }
+
+ if (encodeTo is "RGB555" or "RGB565" or "RGB888"
+ or "BGR555" or "BGR565" or "BGR888")
+ {
+ var bytesPerPixel = encodeTo is "RGB888" or "BGR888" ? 3 : 2;
+ var bytes = new byte[mat.Width * mat.Height * bytesPerPixel];
+ uint index = 0;
+ var span = mat.GetDataByteSpan();
+ for (int i = 0; i < span.Length;)
+ {
+ byte b = span[i++];
+ byte g;
+ byte r;
+
+ if (mat.NumberOfChannels == 1) // 8 bit safe-guard
+ {
+ r = g = b;
+ }
+ else
+ {
+ g = span[i++];
+ r = span[i++];
+ }
+
+ if (mat.NumberOfChannels == 4) i++; // skip alpha
+
+ switch (encodeTo)
+ {
+ case "RGB555":
+ var rgb555 = (ushort) (((r & 0b11111000) << 7) | ((g & 0b11111000) << 2) | (b >> 3));
+ BitExtensions.ToBytesLittleEndian(rgb555, bytes, index);
+ index += 2;
+ break;
+ case "RGB565":
+ var rgb565 = (ushort) (((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3));
+ BitExtensions.ToBytesLittleEndian(rgb565, bytes, index);
+ index += 2;
+ break;
+ case "RGB888":
+ bytes[index++] = r;
+ bytes[index++] = g;
+ bytes[index++] = b;
+ break;
+ }
+ }
+
+ return bytes;
+ }
+
+ throw new NotSupportedException($"The encode type: {encodeTo} is not supported.");
+ }
+
+ public static Mat DecodeImage(byte[] bytes, string decodeFrom, Size resolution)
+ {
+ if (decodeFrom is "PNG" or "JPG" or "JPEG" or "JP2" or "BMP" or "TIF" or "TIFF" or "PPM" or "PMG" or "SR" or "RAS")
+ {
+ var mat = new Mat();
+ CvInvoke.Imdecode(bytes, ImreadModes.AnyColor, mat);
+ return mat;
+ }
+
+ if (decodeFrom is "RGB555" or "RGB565" or "RGB888"
+ or "BGR555" or "BGR565" or "BGR888")
+ {
+ var mat = new Mat(resolution, DepthType.Cv8U, 3);
+ var span = mat.GetDataByteSpan();
+ var pixel = 0;
+ for (int i = 0; i < bytes.Length;)
+ {
+ switch (decodeFrom)
+ {
+ case "RGB555":
+ ushort rgb555 = BitExtensions.ToUShortLittleEndian(bytes, i);
+ span[pixel++] = (byte)((rgb555 & 0b00000000_00011111) << 3); // b
+ span[pixel++] = (byte)((rgb555 & 0b00000011_11100000) >> 2); // g
+ span[pixel++] = (byte)((rgb555 & 0b01111100_00000000) >> 7); // r
+ i += 2;
+ break;
+ case "RGB565":
+ ushort rgb565 = BitExtensions.ToUShortLittleEndian(bytes, i);
+ span[pixel++] = (byte)((rgb565 & 0b00000000_00011111) << 3); // b
+ span[pixel++] = (byte)((rgb565 & 0b00000111_11100000) >> 3); // g
+ span[pixel++] = (byte)((rgb565 & 0b11111000_00000000) >> 8); // r
+ i += 2;
+ break;
+ case "RGB888":
+ span[pixel++] = bytes[i + 2]; // b
+ span[pixel++] = bytes[i + 1]; // g
+ span[pixel++] = bytes[i]; // r
+ i += 3;
+ break;
+ case "BGR555":
+ ushort bgr555 = BitExtensions.ToUShortLittleEndian(bytes, i);
+ span[pixel++] = (byte)((bgr555 & 0b01111100_00000000) >> 7); // b
+ span[pixel++] = (byte)((bgr555 & 0b00000011_11100000) >> 2); // g
+ span[pixel++] = (byte)((bgr555 & 0b00000000_00011111) << 3); // r
+ i += 2;
+ break;
+ case "BGR565":
+ ushort bgr565 = BitExtensions.ToUShortLittleEndian(bytes, i);
+ span[pixel++] = (byte)((bgr565 & 0b11111000_00000000) >> 8); // b
+ span[pixel++] = (byte)((bgr565 & 0b00000111_11100000) >> 3); // g
+ span[pixel++] = (byte)((bgr565 & 0b00000000_00011111) << 3); // r
+ i += 2;
+ break;
+ case "BGR888":
+ span[pixel++] = bytes[i]; // b
+ span[pixel++] = bytes[i+1]; // g
+ span[pixel++] = bytes[i+2]; // r
+ i += 3;
+ break;
+ }
+ }
+ return mat;
+ }
+
+ throw new NotSupportedException($"The decode type: {decodeFrom} is not supported.");
+ }
+
+ public static Mat DecodeImage(byte[] bytes, string decodeFrom, uint resolutionX = 0, uint resolutionY = 0)
+ => DecodeImage(bytes, decodeFrom, new Size((int) resolutionX, (int) resolutionY));
+
+
+ #endregion
+ }
+}