/* * 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 System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Threading.Tasks; using BinarySerialization; using Emgu.CV; using Emgu.CV.CvEnum; using UVtools.Core.Extensions; using UVtools.Core.Layers; using UVtools.Core.Operations; namespace UVtools.Core.FileFormats { public class LGSFile : FileFormat { #region Sub Classes #region Header public class Header { public const string NameValue = "Longer3D"; /// /// Gets the model name /// [FieldOrder(0)] [FieldLength(8)] public string Name { get; set; } = NameValue; // 0x00: [FieldOrder(1)] public uint Uint_08 { get; set; } = 1; // 0x08: 0xff000001 ? [FieldOrder(2)] public uint Uint_0c { get; set; } = 1; // 0x0c: 1 ? [FieldOrder(3)] public uint PrinterModel { get; set; } = 30; // 10, 30, 120, 4000 (4k), 4500 (4k mono) [FieldOrder(4)] public uint Uint_14 { get; set; } = 0; // 0x14: 0 ? [FieldOrder(5)] public uint MagicKey { get; set; } = 34; // 0x18: 34 [FieldOrder(6)] public float PixelPerMmX { get; set; } = 15.404f; [FieldOrder(7)] public float PixelPerMmY { get; set; } = 4.866f; [FieldOrder(8)] public float ResolutionX { get; set; } [FieldOrder(9)] public float ResolutionY { get; set; } [FieldOrder(10)] public float LayerHeight { get; set; } [FieldOrder(11)] public float ExposureTimeMs { get; set; } [FieldOrder(12)] public float BottomExposureTimeMs { get; set; } [FieldOrder(13)] public float Float_38 { get; set; } = 10; // 0x38: 10 [FieldOrder(14)] public float LightOffDelayMs { get; set; } = 2000; [FieldOrder(15)] public float BottomLightOffDelayMs { get; set; } [FieldOrder(16)] public float BottomHeight { get; set; } [FieldOrder(17)] public float Float_48 { get; set; } = 0.6f; // 0x48: 0.6 [FieldOrder(18)] public float BottomLiftHeight { get; set; } = 4; [FieldOrder(19)] public float LiftHeight { get; set; } [FieldOrder(20)] public float LiftSpeed { get; set; } = 150; [FieldOrder(21)] public float LiftSpeed_ { get; set; } = 150; [FieldOrder(22)] public float BottomLiftSpeed { get; set; } = 90; [FieldOrder(23)] public float BottomLiftSpeed_ { get; set; } = 90; [FieldOrder(24)] public float Float_64 { get; set; } = 5; // 0x64: 5? [FieldOrder(25)] public float Float_68 { get; set; } = 60; // 0x68: 60? [FieldOrder(26)] public float Float_6c { get; set; } = 10; // 0x6c: 10? [FieldOrder(27)] public float Float_70 { get; set; } = 600; // 0x70: 600? [FieldOrder(28)] public float Float_74 { get; set; } = 600; // 0x70: 600? [FieldOrder(29)] public float Float_78 { get; set; } = 2; // 0x78: 2? [FieldOrder(30)] public float Float_7c { get; set; } = 0.2f; // 0x7c: 0.2? [FieldOrder(31)] public float Float_80 { get; set; } = 60; // 0x80: 60? [FieldOrder(32)] public float Float_84 { get; set; } = 1; // 0x84: 1? [FieldOrder(33)] public float Float_88 { get; set; } = 6; // 0x88: 6? [FieldOrder(34)] public float Float_8c { get; set; } = 150; // 0x8c: 150 ? [FieldOrder(35)] public float Float_90 { get; set; } = 1001; // 0x90: 1001 ? [FieldOrder(36)] public float MachineZ { get; set; } = 140;// 0x94: 140 for lgs10, 170 for lgs30, 150 for lgs120, 190 for lgs4k [FieldOrder(37)] public uint Uint_98 { get; set; } // 0x98: 0 ? [FieldOrder(38)] public uint Uint_9c { get; set; } // 0x9c: 0 ? [FieldOrder(39)] public uint Uint_a0 { get; set; } // 0xa0: 0 ? [FieldOrder(40)] public uint LayerCount { get; set; } [FieldOrder(41)] public uint Uint_a8 { get; set; } = 4; // 0xa8: 4 ? [FieldOrder(42)] public uint PreviewSizeX { get; set; } = 120; [FieldOrder(43)] public uint PreviewSizeY { get; set; } = 150; } #endregion #region LGS120PngPreview public class LGS120PngPreview { public const ushort ResolutionX = 1200; public const ushort ResolutionY = 1600; [FieldOrder(0)] public uint DataSize { get; set; } [FieldOrder(1)] [FieldLength(nameof(DataSize))] public byte[] PngBytes { get; set; } [FieldOrder(2)] public ushort Padding { get; set; } public void Encode(Mat mat) { mat ??= EmguExtensions.InitMat(new Size(ResolutionX, ResolutionY), 3); if (mat.Width != ResolutionX || mat.Height != ResolutionY) { using var resizeMat = new Mat(); CvInvoke.Resize(mat, resizeMat, new Size(ResolutionX, ResolutionY)); PngBytes = resizeMat.GetPngByes(); } else { PngBytes = mat.GetPngByes(); } } public Mat Decode(bool consumeRle = true) { var mat = new Mat(); CvInvoke.Imdecode(PngBytes, ImreadModes.AnyColor, mat); if (consumeRle) PngBytes = null; return mat; } } #endregion #region LayerData public class LayerDef { [Ignore] public LGSFile Parent { get; set; } [FieldOrder(0)] public uint DataSize { get; set; } [FieldOrder(1)] [FieldLength(nameof(DataSize))] public byte[] EncodedRle { get; set; } public LayerDef() { } public LayerDef(LGSFile parent) { Parent = parent; } public unsafe byte[] Encode(Mat mat) { List rawData = new(); List chunk = new(); if (Parent.HeaderSettings.PrinterModel is 4000 or 4500) { CvInvoke.Rotate(mat, mat, RotateFlags.Rotate90Clockwise); } var spanMat = mat.GetBytePointer(); var imageLength = mat.GetLength(); uint span = 0; byte lc = 0; void addSpan(){ chunk.Clear(); for (; span > 0; span >>= 4) { chunk.Insert(0, (byte)((byte)(span & 0xf) | (lc & 0xf0))); } rawData.AddRange(chunk.ToArray()); } for (int i = 0; i < imageLength; i++) { byte c = (byte) (spanMat[i] & 0xf0); if (c == lc) { span++; } else { addSpan(); span = 1; } lc = c; } addSpan(); EncodedRle = rawData.ToArray(); DataSize = (uint) EncodedRle.Length; if (Parent.HeaderSettings.PrinterModel is 4000 or 4500) { CvInvoke.Rotate(mat, mat, RotateFlags.Rotate90CounterClockwise); } return EncodedRle; } public Mat Decode(bool consumeRle = true) { // lgs10/30 --------> // lgs120/4k From Y bottom to top Y var mat = EmguExtensions.InitMat(Parent.HeaderSettings.PrinterModel is 4000 or 4500 ? Parent.Resolution.Exchange() : Parent.Resolution); //var matSpan = mat.GetBytePointer(); var imageLength = mat.GetLength(); int pixelPos = 0; for (var i = 0; i < EncodedRle.Length; i++) { var b = EncodedRle[i]; byte colorNibble = (byte)(b >> 4); byte color = (byte)(colorNibble << 4 | colorNibble); int repeat = b & 0xf; while (i + 1 < EncodedRle.Length && (EncodedRle[i + 1] >> 4) == colorNibble) { i++; repeat = (repeat << 4) | (EncodedRle[i] & 0xf); } if (pixelPos >= imageLength) { throw new FileLoadException($"Too much buffer, expected: {imageLength}, got: {pixelPos}"); } mat.FillSpan(ref pixelPos, repeat, color); //if (repeat <= 0) continue; /*while (repeat-- > 0) { matSpan[pixel++] = color; }*/ } if (pixelPos != imageLength) { throw new FileLoadException($"Incomplete buffer, expected: {imageLength}, got: {pixelPos}"); } if (consumeRle) EncodedRle = null; if (Parent.HeaderSettings.PrinterModel is 4000 or 4500) { CvInvoke.Rotate(mat, mat, RotateFlags.Rotate90CounterClockwise); } return mat; } } #endregion #endregion #region Properties public Header HeaderSettings { get; protected internal set; } = new(); public override FileFormatType FileType => FileFormatType.Binary; public override FileExtension[] FileExtensions { get; } = { new (typeof(LGSFile), "lgs", "Longer Orange 10"), new (typeof(LGSFile), "lgs30", "Longer Orange 30"), new (typeof(LGSFile), "lgs120", "Longer Orange 120"), new (typeof(LGSFile), "lgs4k", "Longer Orange 4k"), }; public override PrintParameterModifier[] PrintParameterModifiers { get; } = { PrintParameterModifier.BottomLayerCount, PrintParameterModifier.BottomLightOffDelay, PrintParameterModifier.LightOffDelay, PrintParameterModifier.BottomExposureTime, PrintParameterModifier.ExposureTime, PrintParameterModifier.BottomLiftHeight, PrintParameterModifier.BottomLiftSpeed, PrintParameterModifier.LiftHeight, PrintParameterModifier.LiftSpeed, }; public override Size[] ThumbnailsOriginalSize { get; } = {new(120, 150)}; public override uint ResolutionX { get => (uint) HeaderSettings.ResolutionX; set { HeaderSettings.ResolutionX = value; RaisePropertyChanged(); } } public override uint ResolutionY { get => (uint)HeaderSettings.ResolutionY; set { HeaderSettings.ResolutionY = value; RaisePropertyChanged(); } } public override float Xppmm { get => HeaderSettings.PixelPerMmX > 0 ? HeaderSettings.PixelPerMmX : base.Xppmm; set { HeaderSettings.PixelPerMmX = value; base.Xppmm = value; } } public override float Yppmm { get => HeaderSettings.PixelPerMmY > 0 ? HeaderSettings.PixelPerMmY : base.Yppmm; set { HeaderSettings.PixelPerMmY = value; base.Yppmm = value; } } public override float DisplayWidth { get => ResolutionX / HeaderSettings.PixelPerMmX; set { } } public override float DisplayHeight { get => ResolutionY / HeaderSettings.PixelPerMmY; set { } } public override float MachineZ { get => HeaderSettings.MachineZ > 0 ? HeaderSettings.MachineZ : base.MachineZ; set => base.MachineZ = HeaderSettings.MachineZ = (float)Math.Round(value, 2); } public override Enumerations.FlipDirection DisplayMirror { get => Enumerations.FlipDirection.Horizontally; set { } } public override float LayerHeight { get => HeaderSettings.LayerHeight; set { HeaderSettings.LayerHeight = Layer.RoundHeight(value); RaisePropertyChanged(); } } public override uint LayerCount { get => base.LayerCount; set => base.LayerCount = HeaderSettings.LayerCount = base.LayerCount; } public override ushort BottomLayerCount { get => (ushort) (HeaderSettings.BottomHeight / LayerHeight); set { HeaderSettings.BottomHeight = value * LayerHeight; base.BottomLayerCount = value; } } public override float BottomLightOffDelay { get => TimeExtensions.MillisecondsToSeconds(HeaderSettings.BottomLightOffDelayMs); set { HeaderSettings.BottomLightOffDelayMs = TimeExtensions.SecondsToMilliseconds(value); base.BottomLightOffDelay = value; } } public override float LightOffDelay { get => TimeExtensions.MillisecondsToSeconds(HeaderSettings.LightOffDelayMs); set { HeaderSettings.LightOffDelayMs = TimeExtensions.SecondsToMilliseconds(value); base.LightOffDelay = value; } } public override float BottomWaitTimeBeforeCure { get => base.BottomWaitTimeBeforeCure; set { SetBottomLightOffDelay(value); base.BottomWaitTimeBeforeCure = value; } } public override float WaitTimeBeforeCure { get => base.WaitTimeBeforeCure; set { SetNormalLightOffDelay(value); base.WaitTimeBeforeCure = value; } } public override float BottomExposureTime { get => TimeExtensions.MillisecondsToSeconds(HeaderSettings.BottomExposureTimeMs); set { HeaderSettings.BottomExposureTimeMs = TimeExtensions.SecondsToMilliseconds(value); base.BottomExposureTime = value; } } public override float ExposureTime { get => TimeExtensions.MillisecondsToSeconds(HeaderSettings.ExposureTimeMs); set { HeaderSettings.ExposureTimeMs = TimeExtensions.SecondsToMilliseconds(value); base.ExposureTime = value; } } public override float BottomLiftHeight { get => HeaderSettings.BottomLiftHeight; set => base.BottomLiftHeight = HeaderSettings.BottomLiftHeight = value; } public override float LiftHeight { get => HeaderSettings.LiftHeight; set => base.LiftHeight = HeaderSettings.LiftHeight = value; } public override float BottomLiftSpeed { get => HeaderSettings.BottomLiftSpeed; set => base.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed_ = value; } public override float LiftSpeed { get => HeaderSettings.LiftSpeed; set => base.LiftSpeed = HeaderSettings.LiftSpeed = HeaderSettings.LiftSpeed_ = value; } public override float BottomRetractSpeed => RetractSpeed; public override float RetractSpeed => LiftSpeed; /*public override float PrintTime => 0; public override float UsedMaterial => 0; public override float MaterialCost => 0; public override string MaterialName => "Unknown"; public override string MachineName => null;*/ public override object[] Configs => new object[] { HeaderSettings }; #endregion #region Constructors public LGSFile() { } #endregion #region Methods protected override void EncodeInternally(OperationProgress progress) { if (FileEndsWith(".lgs")) // Longer Orange 10 { MachineZ = 140; HeaderSettings.PrinterModel = 10; } else if (FileEndsWith(".lgs30")) // Longer Orange 30 { MachineZ = 170; HeaderSettings.PrinterModel = 30; } else if (FileEndsWith(".lgs120")) // Longer Orange 120 { MachineZ = 150; HeaderSettings.PrinterModel = 120; } else if (FileEndsWith(".lgs4k")) // Longer Orange 4K & Mono { MachineZ = 190; if(HeaderSettings.PrinterModel is not 4000 and not 4500) HeaderSettings.PrinterModel = 4500; } //uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); using (var outputFile = new FileStream(FileFullPath, FileMode.Create, FileAccess.Write)) { outputFile.WriteSerialize(HeaderSettings); outputFile.WriteBytes(EncodeImage(DATATYPE_RGB565_BE, Thumbnails[0])); if (HeaderSettings.PrinterModel == 120) { // Insert PNG here var mat = GetThumbnail(true); var pngPreview = new LGS120PngPreview(); pngPreview.Encode(mat); outputFile.WriteSerialize(pngPreview); } progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); var layerData = new LayerDef[LayerCount]; foreach (var batch in BatchLayersIndexes()) { Parallel.ForEach(batch, CoreSettings.ParallelOptions, layerIndex => { if (progress.Token.IsCancellationRequested) return; using (var mat = this[layerIndex].LayerMat) { layerData[layerIndex] = new LayerDef(this); layerData[layerIndex].Encode(mat); } progress.LockAndIncrement(); }); foreach (var layerIndex in batch) { progress.Token.ThrowIfCancellationRequested(); outputFile.WriteSerialize(layerData[layerIndex]); layerData[layerIndex].EncodedRle = null; // Free this } } progress.ItemName = "Saving layers"; progress.ProcessedItems = 0; for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { progress.Token.ThrowIfCancellationRequested(); outputFile.WriteSerialize(layerData[layerIndex]); progress++; } } Debug.WriteLine("Encode Results:"); Debug.WriteLine(HeaderSettings); Debug.WriteLine("-End-"); } protected override void DecodeInternally(OperationProgress progress) { using var inputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Read); HeaderSettings = Helpers.Deserialize
(inputFile); if (HeaderSettings.Name != Header.NameValue) { throw new FileLoadException("Not a valid LGS file!", FileFullPath); } //if (HeaderSettings.PrinterModel is 10 or 30 or 120) //{ // Fix inconsistencies found of different version of plugin and slicers if (ResolutionX > ResolutionY) { (ResolutionX, ResolutionY) = (ResolutionY, ResolutionX); } //} var previewSize = HeaderSettings.PreviewSizeX * HeaderSettings.PreviewSizeY * 2; var previewData = inputFile.ReadBytes(previewSize); Thumbnails[0] = DecodeImage(DATATYPE_RGB565_BE, previewData, HeaderSettings.PreviewSizeX, HeaderSettings.PreviewSizeY); if (HeaderSettings.PrinterModel == 120) { var pngPreview = Helpers.Deserialize(inputFile); } LayerManager.Init(HeaderSettings.LayerCount, DecodeType == FileDecodeType.Partial); var layerData = new LayerDef[LayerCount]; if (DecodeType == FileDecodeType.Full) { progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); foreach (var batch in BatchLayersIndexes()) { foreach (var layerIndex in batch) { progress.Token.ThrowIfCancellationRequested(); layerData[layerIndex] = Helpers.Deserialize(inputFile); layerData[layerIndex].Parent = this; } Parallel.ForEach(batch, CoreSettings.ParallelOptions, layerIndex => { if (progress.Token.IsCancellationRequested) return; using var mat = layerData[layerIndex].Decode(); this[layerIndex] = new Layer((uint)layerIndex, mat, this); progress.LockAndIncrement(); }); } } LayerManager.RebuildLayersProperties(); } protected override void PartialSaveInternally(OperationProgress progress) { using var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write); outputFile.Seek(0, SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); } #endregion } }