From db04afa97ed6cb13e7bccead77974c75d4a79eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 11 Apr 2020 21:16:49 +0100 Subject: [PATCH] Pre0.2 commit --- PrusaSL1Reader/CbddlpFile.cs | 630 ++++++++++++++++++---- PrusaSL1Reader/FileExtension.cs | 89 +++ PrusaSL1Reader/FileFormat.cs | 116 +++- PrusaSL1Reader/Helpers.cs | 52 ++ PrusaSL1Reader/IFileFormat.cs | 67 ++- PrusaSL1Reader/ObjectExtensions.cs | 24 + PrusaSL1Reader/PrusaSL1Reader.csproj | 11 +- PrusaSL1Reader/SL1File.cs | 168 +++++- PrusaSL1Reader/StringExtensions.cs | 11 + PrusaSL1Viewer/App.config | 24 +- PrusaSL1Viewer/FrmMain.Designer.cs | 16 +- PrusaSL1Viewer/FrmMain.cs | 47 +- PrusaSL1Viewer/ImageSharpExtensions.cs | 36 ++ PrusaSL1Viewer/Program.cs | 12 +- PrusaSL1Viewer/Properties/AssemblyInfo.cs | 4 +- PrusaSL1Viewer/PrusaSL1Viewer.csproj | 69 +++ PrusaSL1Viewer/packages.config | 23 + 17 files changed, 1251 insertions(+), 148 deletions(-) create mode 100644 PrusaSL1Reader/FileExtension.cs create mode 100644 PrusaSL1Reader/ObjectExtensions.cs create mode 100644 PrusaSL1Viewer/ImageSharpExtensions.cs create mode 100644 PrusaSL1Viewer/packages.config diff --git a/PrusaSL1Reader/CbddlpFile.cs b/PrusaSL1Reader/CbddlpFile.cs index af461d8..e2fad08 100644 --- a/PrusaSL1Reader/CbddlpFile.cs +++ b/PrusaSL1Reader/CbddlpFile.cs @@ -5,165 +5,605 @@ * 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.IO; -using System.Runtime.InteropServices; +using BinarySerialization; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; namespace PrusaSL1Reader { public class CbddlpFile : FileFormat { - const uint SPI_FILE_MAGIC_BASE = 0x12FD0000; - const int SPECIAL_BIT = 1 << 1; - const int SPECIAL_BIT_MASK = ~SPECIAL_BIT; + #region Constants + private const uint SPI_FILE_MAGIC_BASE = 0x12FD0019; + private const int SPECIAL_BIT = 1 << 1; + private const int SPECIAL_BIT_MASK = ~SPECIAL_BIT; + private const ushort REPEATRGB15MASK = (1 << 5); - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct Header + private const byte RLE8EncodingLimit = 125; + private const ushort RLE16EncodingLimit = 0x1000; + #endregion + + #region Sub Classes + #region Header + public class Header { - public uint Magic; - public uint Version; - public float BedSizeX; - public float BedSizeY; - public float BedSizeZ; - public uint Unknown1; - public uint Unknown2; - public uint Unknown3; - public float LayerHeightMilimeter; - public float LayerExposureSeconds; - public float BottomExposureSeconds; - public float LayerOffTime; - public uint BottomLayersCount; - public uint ResolutionX; - public uint ResolutionY; - public uint PreviewOneOffsetAddress; - public uint LayersDefinitionOffsetAddress; - public uint LayerCount; - public uint PreviewTwoOffsetAddress; - public uint PrintTime; - public uint ProjectorType; - public uint PrintParametersOffsetAddress; - public uint PrintParametersSize; - public uint AntiAliasLevel; - public ushort LightPWM; - public ushort BottomLightPWM; - public uint Padding1; - public uint Padding2; - public uint Padding3; + + [FieldOrder(0)] public uint Magic { get; set; } // 00 + [FieldOrder(1)] public uint Version { get; set; } // 04 + [FieldOrder(2)] public float BedSizeX { get; set; } // 08 + [FieldOrder(3)] public float BedSizeY { get; set; } // 12 + [FieldOrder(4)] public float BedSizeZ { get; set; } // 20 + [FieldOrder(5)] public uint Unknown1 { get; set; } + [FieldOrder(6)] public uint Unknown2 { get; set; } + [FieldOrder(7)] public uint Unknown3 { get; set; } + [FieldOrder(8)] public float LayerHeightMilimeter { get; set; } // 20 + [FieldOrder(9)] public float LayerExposureSeconds { get; set; } // 24: Layer exposure (in seconds) + [FieldOrder(10)] public float BottomExposureSeconds { get; set; } // 28: Bottom layers exporsure (in seconds) + [FieldOrder(11)] public float LayerOffTime { get; set; } // 2c: Layer off time (in seconds) + [FieldOrder(12)] public uint BottomLayersCount { get; set; } // 30: Number of bottom layers + [FieldOrder(13)] public uint ResolutionX { get; set; } // 34: + [FieldOrder(14)] public uint ResolutionY { get; set; } // 38: + [FieldOrder(15)] public uint PreviewOneOffsetAddress { get; set; }// 3c: Offset of the high-res preview + [FieldOrder(16)] public uint LayersDefinitionOffsetAddress { get; set; }// 40: Offset of the layer definitions + [FieldOrder(17)] public uint LayerCount { get; set; } // 44: + [FieldOrder(18)] public uint PreviewTwoOffsetAddress { get; set; }// 48: Offset of the low-rew preview + [FieldOrder(19)] public uint PrintTime { get; set; } // 4c: In seconds + [FieldOrder(20)] public uint ProjectorType { get; set; } // 0 = CAST, 1 = LCD_X_MIRROR + [FieldOrder(21)] public uint PrintParametersOffsetAddress { get; set; } // 54: + [FieldOrder(22)] public uint PrintParametersSize { get; set; } // 58: + [FieldOrder(23)] public uint AntiAliasLevel { get; set; } // 5c: + [FieldOrder(24)] public ushort LightPWM { get; set; } // 60: + [FieldOrder(25)] public ushort BottomLightPWM { get; set; } // 62: + [FieldOrder(26)] public uint Padding1 { get; set; } + [FieldOrder(27)] public uint Padding2 { get; set; } + [FieldOrder(28)] public uint Padding3 { get; set; } public override string ToString() { return $"{nameof(Magic)}: {Magic}, {nameof(Version)}: {Version}, {nameof(BedSizeX)}: {BedSizeX}, {nameof(BedSizeY)}: {BedSizeY}, {nameof(BedSizeZ)}: {BedSizeZ}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(LayerHeightMilimeter)}: {LayerHeightMilimeter}, {nameof(LayerExposureSeconds)}: {LayerExposureSeconds}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {nameof(LayerOffTime)}: {LayerOffTime}, {nameof(BottomLayersCount)}: {BottomLayersCount}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(PreviewOneOffsetAddress)}: {PreviewOneOffsetAddress}, {nameof(LayersDefinitionOffsetAddress)}: {LayersDefinitionOffsetAddress}, {nameof(LayerCount)}: {LayerCount}, {nameof(PreviewTwoOffsetAddress)}: {PreviewTwoOffsetAddress}, {nameof(PrintTime)}: {PrintTime}, {nameof(ProjectorType)}: {ProjectorType}, {nameof(PrintParametersOffsetAddress)}: {PrintParametersOffsetAddress}, {nameof(PrintParametersSize)}: {PrintParametersSize}, {nameof(AntiAliasLevel)}: {AntiAliasLevel}, {nameof(LightPWM)}: {LightPWM}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(Padding3)}: {Padding3}"; } } + #endregion - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PrintParameters + #region PrintParameters + public class PrintParameters { - public float BottomLiftHeight; - public float BottomLiftSpeed; - public float LiftHeight; - public float LiftingSpeed; - public float RetractSpeed; - public float VolumeMl; - public float WeightG; - public float CostDollars; - public float BottomLightOffDelay; - public float LightOffDelay; - public uint BottomLayerCount; - public uint P1; - public uint P2; - public uint P3; - public uint P4; + [FieldOrder(0)] public float BottomLiftHeight { get; set; } + [FieldOrder(1)] public float BottomLiftSpeed { get; set; } + [FieldOrder(2)] public float LiftHeight { get; set; } + [FieldOrder(3)] public float LiftingSpeed { get; set; } + [FieldOrder(4)] public float RetractSpeed { get; set; } + [FieldOrder(5)] public float VolumeMl { get; set; } + [FieldOrder(6)] public float WeightG { get; set; } + [FieldOrder(7)] public float CostDollars { get; set; } + [FieldOrder(8)] public float BottomLightOffDelay { get; set; } + [FieldOrder(9)] public float LightOffDelay { get; set; } + [FieldOrder(10)] public uint BottomLayerCount { get; set; } + [FieldOrder(11)] public uint P1 { get; set; } + [FieldOrder(12)] public uint P2 { get; set; } + [FieldOrder(13)] public uint P3 { get; set; } + [FieldOrder(14)] public uint P4 { get; set; } public override string ToString() { return $"{nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftingSpeed)}: {LiftingSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(VolumeMl)}: {VolumeMl}, {nameof(WeightG)}: {WeightG}, {nameof(CostDollars)}: {CostDollars}, {nameof(BottomLightOffDelay)}: {BottomLightOffDelay}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(P1)}: {P1}, {nameof(P2)}: {P2}, {nameof(P3)}: {P3}, {nameof(P4)}: {P4}"; } } + #endregion - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct Layer + #region MachineInfo + + public class MachineInfo { - public float LayerPositionZ; - public float LayerExposure; - public float LayerOffTimeSeconds; - public uint DataAddress; - public uint DataSize; - public uint Unknown1; - public uint Unknown2; - public uint Unknown3; - public uint Unknown4; + [FieldOrder(0)] public uint d1 { get; set; } + [FieldOrder(1)] public uint d2 { get; set; } + [FieldOrder(2)] public uint d3 { get; set; } + [FieldOrder(3)] public uint d4 { get; set; } + [FieldOrder(4)] public uint d5 { get; set; } + [FieldOrder(5)] public uint d6 { get; set; } + [FieldOrder(6)] public uint d7 { get; set; } + [FieldOrder(7)] public uint MachineNameAddress { get; set; } + [FieldOrder(8)] public uint MachineNameSize { get; set; } + [FieldOrder(9)] public uint d8 { get; set; } + [FieldOrder(10)] public uint d9 { get; set; } + [FieldOrder(11)] public uint d10 { get; set; } + [FieldOrder(12)] public uint d11 { get; set; } + [FieldOrder(13)] public uint d12 { get; set; } + [FieldOrder(14)] public uint d13 { get; set; } + [FieldOrder(15)] public uint d14 { get; set; } + [FieldOrder(16)] public uint d15 { get; set; } + [FieldOrder(17)] public uint d16 { get; set; } + [FieldOrder(18)] public uint d17 { get; set; } + + [FieldOrder(19)] [FieldLength(nameof(MachineNameSize))] + public string MachineName { get; set; } + + public override string ToString() + { + return $"{nameof(d1)}: {d1}, {nameof(d2)}: {d2}, {nameof(d3)}: {d3}, {nameof(d4)}: {d4}, {nameof(d5)}: {d5}, {nameof(d6)}: {d6}, {nameof(d7)}: {d7}, {nameof(MachineNameAddress)}: {MachineNameAddress}, {nameof(MachineNameSize)}: {MachineNameSize}, {nameof(d8)}: {d8}, {nameof(d9)}: {d9}, {nameof(d10)}: {d10}, {nameof(d11)}: {d11}, {nameof(d12)}: {d12}, {nameof(d13)}: {d13}, {nameof(d14)}: {d14}, {nameof(d15)}: {d15}, {nameof(d16)}: {d16}, {nameof(d17)}: {d17}, {nameof(MachineName)}: {MachineName}"; + } + } + + #endregion + + #region Preview + public class Preview + { + [FieldOrder(0)] public uint ResolutionX { get; set; } + [FieldOrder(1)] public uint ResolutionY { get; set; } + [FieldOrder(2)] public uint ImageOffset { get; set; } + [FieldOrder(3)] public uint ImageLength { get; set; } + [FieldOrder(4)] public uint Unknown1 { get; set; } + [FieldOrder(5)] public uint Unknown2 { get; set; } + [FieldOrder(6)] public uint Unknown3 { get; set; } + [FieldOrder(7)] public uint Unknown4 { get; set; } + + public override string ToString() + { + return $"{nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(ImageOffset)}: {ImageOffset}, {nameof(ImageLength)}: {ImageLength}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; + } + } + + #endregion + + #region Layer + public class Layer + { + [FieldOrder(0)] public float LayerPositionZ { get; set; } + [FieldOrder(1)] public float LayerExposure { get; set; } + [FieldOrder(2)] public float LayerOffTimeSeconds { get; set; } + [FieldOrder(3)] public uint DataAddress { get; set; } + [FieldOrder(4)] public uint DataSize { get; set; } + [FieldOrder(5)] public uint Unknown1 { get; set; } + [FieldOrder(6)] public uint Unknown2 { get; set; } + [FieldOrder(7)] public uint Unknown3 { get; set; } + [FieldOrder(8)] public uint Unknown4 { get; set; } public override string ToString() { return $"{nameof(LayerPositionZ)}: {LayerPositionZ}, {nameof(LayerExposure)}: {LayerExposure}, {nameof(LayerOffTimeSeconds)}: {LayerOffTimeSeconds}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; } } + #endregion - public Header HeaderSettings { get; private set; } - public PrintParameters PrintParametersSettings { get; private set; } + #endregion - public List Layers { get; } = new List(); + #region Properties + public FileStream InputFile { get; private set; } + public FileStream OutputFile { get; private set; } + public Header HeaderSettings { get; protected internal set; } + public PrintParameters PrintParametersSettings { get; protected internal set; } - public override string FileExtension { get; } = "cbddlp"; - public override string FileExtensionName { get; } = "Chitubox DLP Files"; - public override void Load(string fileFullPath) + public MachineInfo MachineInfoSettings { get; protected internal set; } + //public string MachineName { get; protected internal set; } + + public Preview[] Previews { get; protected internal set; } + + public Layer[,] Layers { get; private set; } + + private uint CurrentOffset { get; set; } + + #endregion + + #region Constructors + public CbddlpFile() { - FileValidation(fileFullPath); - FileFullPath = fileFullPath; + Previews = new Preview[ThumbnailsCount]; + } + #endregion - Layers.Clear(); + #region Overrides - BinaryReader binReader = new BinaryReader(File.Open(FileFullPath, FileMode.Open)); + public override string FileFullPath { get; protected set; } + + public override FileExtension[] ValidFiles { get; } = { + new FileExtension("cbddlp", "Chitubox DLP Files"), + }; + public override byte ThumbnailsCount { get; } = 2; + public override Image[] Thumbnails { get; protected internal set; } + + public override void BeginEncode(string fileFullPath) + { + CurrentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); + Layers = new Layer[HeaderSettings.LayerCount, HeaderSettings.AntiAliasLevel]; + OutputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write); + + HeaderSettings.Magic = SPI_FILE_MAGIC_BASE; + //HeaderSettings.PreviewOneOffsetAddress = CurrentOffset; + HeaderSettings.PrintParametersSize = (uint)Helpers.Serializer.SizeOf(PrintParametersSettings); + + Helpers.SerializeWriteFileStream(OutputFile, HeaderSettings); + + //OutputFile.Seek((int)HeaderSettings.PreviewOneOffsetAddress, SeekOrigin.Begin); - HeaderSettings = Helpers.ByteToType(binReader); - if (HeaderSettings.Magic != (SPI_FILE_MAGIC_BASE | 0x19)) + + /*for (int i = 0; i < ThumbnailsCount; i++) + { + if (i == 1) + { + HeaderSettings.PreviewTwoOffsetAddress = CurrentOffset; + } + var image = Thumbnails[i]; + Preview preview = new Preview {ResolutionX = (uint)image.Width, ResolutionY = (uint)image.Height}; + List rawData = new List(); + + Rgba32 color; + byte nrOfColor = 0; + Rgba32? prevColor = null; + + for (int y = 0; y < image.Height; y++) + { + Span pixelRowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < image.Width; x++) + { + color = pixelRowSpan[x]; + if (prevColor == null) prevColor = color; + bool isLastPixel = x == (image.Width - 1) && y == (image.Height - 1); + if (color == prevColor && nrOfColor < 0x0FFF && !isLastPixel) + { + nrOfColor++; + } + else + { + + byte R = prevColor.Value.R; + byte G = prevColor.Value.G; + byte B = prevColor.Value.B; + byte X = nrOfColor > 1 ? (byte)1 : (byte)0; + + + // build 2 or 4 bytes (depending on X + // The color (R,G,B) of a pixel spans 2 bytes (little endian) and + // each color component is 5 bits: RRRRR GGG GG X BBBBB + R = (byte)Math.Round(R / 255f * 31f); + G = (byte)Math.Round(G / 255f * 31f); + B = (byte)Math.Round(B / 255f * 31f); + + byte encValue0 = (byte)(R << 3 | G >> 2); + byte encValue1 = (byte) (((G & 0b00000011) << 6) | X << 5 | B); + rawData.Add(encValue0); + rawData.Add(encValue1); + if (X == 1) + { + nrOfColor--; + // write one less than nr of pixels + byte encValue2 = (byte)(nrOfColor >> 8); + byte encValue3 = (byte)(nrOfColor & 0b000000011111111); + // seems like nr bytes pixels have 0011 as start + encValue2 = (byte)(encValue2 | 0b00110000); + rawData.Add(encValue2); + rawData.Add(encValue3); + } + + prevColor = color; + nrOfColor = 1; + } + } + } + + + preview.ImageLength = (uint)rawData.Count; + CurrentOffset += (uint)Helpers.Serializer.SizeOf(preview); + preview.ImageOffset = CurrentOffset; + + Previews[i] = preview; + + using (MemoryStream stream = Helpers.Serialize(preview)) + { + OutputFile.Write(stream.GetBuffer()); + } + + OutputFile.Write(rawData.ToArray()); + + CurrentOffset += (uint)rawData.Count; + } + */ + + if (HeaderSettings.Version == 2) + { + HeaderSettings.PrintParametersOffsetAddress = CurrentOffset; + + Helpers.SerializeWriteFileStream(OutputFile, PrintParametersSettings); + + CurrentOffset += (uint) (Helpers.Serializer.SizeOf(PrintParametersSettings) + + Helpers.Serializer.SizeOf(MachineInfoSettings) - + MachineInfoSettings.MachineNameSize); + + MachineInfoSettings.MachineNameAddress = CurrentOffset; + + Helpers.SerializeWriteFileStream(OutputFile, MachineInfoSettings); + + CurrentOffset += MachineInfoSettings.MachineNameSize; + } + + HeaderSettings.LayersDefinitionOffsetAddress = CurrentOffset; + } + + public override void InsertLayerImageEncode(Image image, uint layerIndex) + { + Layer layer = new Layer(); + List rawData = new List(); + + byte color = 0; + byte black = 0; + byte white = 1; + + byte nrOfColor = 0; + byte prevColor = byte.MaxValue; + + for (int y = 0; y < image.Height; y++) + { + Span pixelRowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < image.Width; x++) + { + color = pixelRowSpan[x].PackedValue < 128 ? black : white; + if (prevColor == byte.MaxValue) prevColor = color; + bool isLastPixel = x == (image.Width - 1) && y == (image.Height - 1); + + if (color == prevColor && nrOfColor< 0x7D && !isLastPixel) + { + nrOfColor++; + } + else + { + byte encValue = (byte)((prevColor << 7) | nrOfColor); // push color (B/W) to highest bit and repetitions to lowest 7 bits. + rawData.Add(encValue); + prevColor = color; + nrOfColor = 1; + } + } + } + + CurrentOffset += (uint)Helpers.Serializer.SizeOf(layer); + + layer.DataAddress = CurrentOffset; + layer.DataSize = (uint)rawData.Count; + layer.LayerPositionZ = layerIndex * HeaderSettings.LayerHeightMilimeter; + layer.LayerOffTimeSeconds = layerIndex < HeaderSettings.BottomLayersCount ? PrintParametersSettings.BottomLightOffDelay : PrintParametersSettings.LightOffDelay; + layer.LayerExposure = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomExposureSeconds : HeaderSettings.LayerExposureSeconds; + Layers[layerIndex, 0] = layer; + + Helpers.SerializeWriteFileStream(OutputFile, layer); + Helpers.WriteFileStream(OutputFile, rawData.ToArray()); + + CurrentOffset += (uint)rawData.Count; + } + + public override void EndEncode() + { + + OutputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(OutputFile, HeaderSettings); + + OutputFile.Close(); + OutputFile.Dispose(); + + Debug.WriteLine("Encode Results:"); + Debug.WriteLine(HeaderSettings); + Debug.WriteLine(Previews[0]); + Debug.WriteLine(Previews[1]); + Debug.WriteLine(PrintParametersSettings); + Debug.WriteLine(MachineInfoSettings); + Debug.WriteLine("-End-"); + } + + public override void Decode(string fileFullPath) + { + DecodeInternal(fileFullPath); + + InputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read); + + //HeaderSettings = Helpers.ByteToType(InputFile); + //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); + HeaderSettings = Helpers.Deserialize
(InputFile); + if (HeaderSettings.Magic != SPI_FILE_MAGIC_BASE) { throw new FileLoadException("Not a valid CBDDLP file!", fileFullPath); } - Debug.WriteLine(HeaderSettings); - - if (HeaderSettings.Version == 2) + if (HeaderSettings.Version == 1 || HeaderSettings.AntiAliasLevel == 0) { - binReader.BaseStream.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); - PrintParametersSettings = Helpers.ByteToType(binReader); - Debug.WriteLine(PrintParametersSettings); + HeaderSettings.AntiAliasLevel = 1; } - uint aaLevel = HeaderSettings.Version == 1 ? 1 : HeaderSettings.AntiAliasLevel; + FileFullPath = fileFullPath; - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - for (int image = 1; image <= HeaderSettings.AntiAliasLevel; image++) + + Debug.Write("Header -> "); + Debug.WriteLine(HeaderSettings); + + for (byte i = 0; i < ThumbnailsCount; i++) { - if (HeaderSettings.AntiAliasLevel > 1) Debug.WriteLine("Image GROUP " + image + "----"); - for (int i = 0; i < HeaderSettings.LayerCount; i++) - { - binReader.BaseStream.Seek(layerOffset, SeekOrigin.Begin); - Layer layer = Helpers.ByteToType(binReader); - Layers.Add(layer); + uint offsetAddress = i == 0 ? HeaderSettings.PreviewOneOffsetAddress : HeaderSettings.PreviewTwoOffsetAddress; + if(offsetAddress == 0) continue; - layerOffset += (uint)Marshal.SizeOf((object)layer); - Debug.WriteLine("LAYER " + i); - Debug.WriteLine(layer); + InputFile.Seek(offsetAddress, SeekOrigin.Begin); + Previews[i] = Helpers.Deserialize(InputFile); + + Debug.Write($"Preview {i} -> "); + Debug.WriteLine(Previews[i]); + + Thumbnails[i] = new Image((int)Previews[i].ResolutionX, (int)Previews[i].ResolutionY); + + InputFile.Seek(Previews[i].ImageOffset, SeekOrigin.Begin); + byte[] rawImageData = new byte[Previews[i].ImageLength]; + InputFile.Read(rawImageData, 0, (int)Previews[i].ImageLength); + int x = 0; + int y = 0; + for (int n = 0; n < Previews[i].ImageLength; n++) + { + uint dot = (uint)(rawImageData[n] & 0xFF | ((rawImageData[++n] & 0xFF) << 8)); + //uint color = ((dot & 0xF800) << 8) | ((dot & 0x07C0) << 5) | ((dot & 0x001F) << 3); + byte red = (byte)(((dot >> 11) & 0x1F) << 3); + byte green = (byte)(((dot >> 6) & 0x1F) << 3); + byte blue = (byte)((dot & 0x1F) << 3); + int repeat = 1; + if ((dot & 0x0020) == 0x0020) + { + repeat += rawImageData[++n] & 0xFF | ((rawImageData[++n] & 0x0F) << 8); + } + + + for (int j = 0; j < repeat; j++) + { + Thumbnails[i][x, y] = new Rgba32(red, green, blue, 255); + x++; + + if (x == Previews[i].ResolutionX) + { + x = 0; + y++; + } + } } } - binReader.Close(); - binReader.Dispose(); + if (HeaderSettings.Version == 2 && HeaderSettings.PrintParametersOffsetAddress > 0) + { + InputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); + PrintParametersSettings = Helpers.Deserialize(InputFile); + Debug.Write("Print Parameters -> "); + Debug.WriteLine(PrintParametersSettings); + + MachineInfoSettings = Helpers.Deserialize(InputFile); + Debug.Write("Machine Info -> "); + Debug.WriteLine(MachineInfoSettings); + /*InputFile.BaseStream.Seek(MachineInfoSettings.MachineNameAddress, SeekOrigin.Begin); + byte[] bytes = InputFile.ReadBytes((int)MachineInfoSettings.MachineNameSize); + MachineName = System.Text.Encoding.UTF8.GetString(bytes); + Debug.WriteLine($"{nameof(MachineName)}: {MachineName}");*/ + } + Layers = new Layer[HeaderSettings.LayerCount, HeaderSettings.AntiAliasLevel]; + uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + Image image = new Image((int)HeaderSettings.BedSizeX, (int)HeaderSettings.BedSizeY); + + + for (uint aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) + { + Debug.WriteLine($"-Image GROUP {aaIndex}-"); + for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + InputFile.Seek(layerOffset, SeekOrigin.Begin); + Layer layer = Helpers.Deserialize(InputFile); + Layers[layerIndex, aaIndex] = layer; + //Layers.Add(layer); + + layerOffset += (uint)Helpers.Serializer.SizeOf(layer); + Debug.Write($"LAYER {layerIndex} -> "); + Debug.WriteLine(layer); + } + } + } + + public override Image GetLayerImage(uint layerIndex) + { + if (layerIndex >= HeaderSettings.LayerCount) + { + throw new IndexOutOfRangeException($"Layer {layerIndex} doesn't exists, out of bounds."); + } + + Image image = new Image((int)HeaderSettings.ResolutionX, (int)HeaderSettings.ResolutionY); + + for (uint aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) + { + Layer layer = Layers[layerIndex, aaIndex]; + InputFile.Seek(layer.DataAddress, SeekOrigin.Begin); + byte[] rawImageData = new byte[(int)layer.DataSize]; + InputFile.Read(rawImageData, 0, (int)layer.DataSize); + uint x = 0; + uint y = 0; + + foreach (byte rle in rawImageData) + { + // From each byte retrieve color (highest bit) and number of pixels of that color (lowest 7 bits) + uint length = (uint)(rle & 0x7F); // turn highest bit of + bool color = (rle & 0x80) == 0x80; // only read 1st bit + + uint x2 = (uint)Math.Min(x + length, HeaderSettings.ResolutionX); + + if (color) + { + for (uint i = x; i < x2; i++) + { + image[(int) i, (int) y] = Helpers.Gray8White; + } + } + + x += length; + + if (x >= HeaderSettings.ResolutionX) + { + length = x - HeaderSettings.ResolutionX; + x = 0; + y++; + x2 = x + length; + + if (color) + { + for (uint i = x; i < x2; i++) + { + image[(int) i, (int) y] = new Gray8(255); + } + } + + x += length; + } + } + } + + return image; } public override float GetHeightFromLayer(uint layerNum) + { + return HeaderSettings.LayerCount; + } + + public override void Clear() + { + ClearInternal(); + + for (byte i = 0; i < ThumbnailsCount; i++) + { + Previews[i] = new Preview(); + } + + Layers = null; + + if (!ReferenceEquals(InputFile, null)) + { + InputFile.Close(); + InputFile.Dispose(); + } + + if (!ReferenceEquals(OutputFile, null)) + { + OutputFile.Close(); + OutputFile.Dispose(); + } + } + + public override bool Convert(Type to, string fileFullPath) { throw new NotImplementedException(); } + + #endregion + + #region Methods + + #endregion } } diff --git a/PrusaSL1Reader/FileExtension.cs b/PrusaSL1Reader/FileExtension.cs new file mode 100644 index 0000000..07df03f --- /dev/null +++ b/PrusaSL1Reader/FileExtension.cs @@ -0,0 +1,89 @@ +/* + * 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.Collections.Generic; + +namespace PrusaSL1Reader +{ + /// + /// Represents a file extension for slicer file formats + /// + public sealed class FileExtension + { + #region Properties + /// + /// Gets the extension name without the dot (.) + /// + public string Extension { get; } + + /// + /// Gets the extension description + /// + public string Description { get; } + + /// + /// Gets the file filter for open and save dialogs + /// + public string Filter => $@"{Description} (*.{Extension})|*.{Extension}"; + #endregion + + #region Constructor + /// + /// Constructor + /// + /// The extension name without the dot (.) + /// The extension description + public FileExtension(string extension, string description) + { + Extension = extension; + Description = description; + } + #endregion + + #region Overrides + + public override string ToString() + { + return $"{nameof(Extension)}: {Extension}, {nameof(Description)}: {Description}"; + } + + private bool Equals(FileExtension other) + { + return Extension == other.Extension; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is FileExtension other && Equals(other); + } + + public override int GetHashCode() + { + return (Extension != null ? Extension.GetHashCode() : 0); + } + + private sealed class ExtensionEqualityComparer : IEqualityComparer + { + public bool Equals(FileExtension x, FileExtension y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return x.Extension == y.Extension; + } + + public int GetHashCode(FileExtension obj) + { + return (obj.Extension != null ? obj.Extension.GetHashCode() : 0); + } + } + + public static IEqualityComparer ExtensionComparer { get; } = new ExtensionEqualityComparer(); + #endregion + } +} diff --git a/PrusaSL1Reader/FileFormat.cs b/PrusaSL1Reader/FileFormat.cs index b3db9f2..4e56254 100644 --- a/PrusaSL1Reader/FileFormat.cs +++ b/PrusaSL1Reader/FileFormat.cs @@ -1,21 +1,123 @@ -using System; +/* + * 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.IO; +using System.Linq; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; namespace PrusaSL1Reader { - public abstract class FileFormat : IFileFormat + /// + /// Slicer representation + /// + public abstract class FileFormat : IFileFormat, IDisposable { - public string FileFullPath { get; protected set; } - public abstract string FileExtension { get; } - public abstract string FileExtensionName { get; } - public abstract void Load(string fileFullPath); + /// + /// Gets the input file path loaded into this + /// + public abstract string FileFullPath { get; protected set; } + + /// + /// Gets the valid file extensions for this + /// + public abstract FileExtension[] ValidFiles { get; } + + /// + /// Gets the thumbnails count present in this file format + /// + public abstract byte ThumbnailsCount { get; } + + /// + /// Gets the thumbnails for this + /// + public abstract Image[] Thumbnails { get; protected internal set; } + + protected FileFormat() + { + Thumbnails = new Image[ThumbnailsCount]; + } + + public bool IsValid() + { + return !ReferenceEquals(FileFullPath, null); + } + + public abstract void BeginEncode(string fileFullPath); + + public abstract void InsertLayerImageEncode(Image image, uint layerIndex); + + public abstract void EndEncode(); + + public abstract void Decode(string fileFullPath); + public abstract Image GetLayerImage(uint layerIndex); + + protected void DecodeInternal(string fileFullPath) + { + Clear(); + FileValidation(fileFullPath); + FileFullPath = fileFullPath; + } public abstract float GetHeightFromLayer(uint layerNum); + public abstract void Clear(); + + protected void ClearInternal() + { + FileFullPath = null; + if (!ReferenceEquals(Thumbnails, null)) + { + for (int i = 0; i < ThumbnailsCount; i++) + { + Thumbnails[i]?.Dispose(); + } + } + } + public abstract bool Convert(Type to, string fileFullPath); + public void FileValidation(string fileFullPath) { if (ReferenceEquals(fileFullPath, null)) throw new ArgumentNullException(nameof(FileFullPath), "fullFilePath can't be null."); if (!File.Exists(fileFullPath)) throw new FileNotFoundException("The specified file does not exists.", fileFullPath); - if (!Path.GetExtension(fileFullPath).Equals($".{FileExtension}")) throw new FileLoadException($"The specified file is not valid, can only open {FileExtension} files.", fileFullPath); + + if (IsExtensionValid(fileFullPath, true)) + { + return; + } + + throw new FileLoadException($"The specified file is not valid.", fileFullPath); + } + + public bool IsExtensionValid(string extension, bool isFilePath = false) + { + extension = isFilePath ? Path.GetExtension(extension)?.Remove(0, 1) : extension; + return ValidFiles.Any(fileExtension => fileExtension.Extension.Equals(extension)); + } + + public string GetFileFilter() + { + string result = String.Empty; + + foreach (var fileExt in ValidFiles) + { + if (!ReferenceEquals(result, string.Empty)) + { + result += '|'; + } + result += fileExt.Filter; + } + + return result; + } + + public void Dispose() + { + Clear(); } } } diff --git a/PrusaSL1Reader/Helpers.cs b/PrusaSL1Reader/Helpers.cs index bf76539..1dfd138 100644 --- a/PrusaSL1Reader/Helpers.cs +++ b/PrusaSL1Reader/Helpers.cs @@ -7,11 +7,16 @@ */ using System.IO; using System.Runtime.InteropServices; +using System.Runtime.Serialization.Formatters.Binary; +using BinarySerialization; +using SixLabors.ImageSharp.PixelFormats; namespace PrusaSL1Reader { public static class Helpers { + public static BinarySerializer Serializer { get; } = new BinarySerializer {Endianness = Endianness.Little }; + public static Gray8 Gray8White { get; } = new Gray8(255); public static T ByteToType(BinaryReader reader) { byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); @@ -22,5 +27,52 @@ namespace PrusaSL1Reader return theStructure; } + + public static byte[] SerializeToBytes(T item) + { + var formatter = new BinaryFormatter(); + using (var stream = new MemoryStream()) + { + formatter.Serialize(stream, item); + stream.Seek(0, SeekOrigin.Begin); + return stream.ToArray(); + } + } + + public static MemoryStream Serialize(object value) + { + MemoryStream stream = new MemoryStream(); + Serializer.Serialize(stream, value); + return stream; + } + + public static T Deserialize(BinaryReader binaryReader) + { + return Deserialize(binaryReader.BaseStream); + } + + public static T Deserialize(Stream stream) + { + return Helpers.Serializer.Deserialize(stream); + } + + public static int WriteFileStream(FileStream fs, MemoryStream stream, uint offset = 0) + { + return WriteFileStream(fs, stream.GetBuffer(), offset); + } + + public static int WriteFileStream(FileStream fs, byte[] bytes, uint offset = 0) + { + fs.Write(bytes, 0, bytes.Length); + return bytes.Length; + } + + public static int SerializeWriteFileStream(FileStream fs, object value, uint offset = 0) + { + using (MemoryStream stream = Helpers.Serialize(value)) + { + return Helpers.WriteFileStream(fs, stream); + } + } } } diff --git a/PrusaSL1Reader/IFileFormat.cs b/PrusaSL1Reader/IFileFormat.cs index 1adb935..cfc3c31 100644 --- a/PrusaSL1Reader/IFileFormat.cs +++ b/PrusaSL1Reader/IFileFormat.cs @@ -5,13 +5,74 @@ * 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 SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + namespace PrusaSL1Reader { + /// + /// Slicer file format representation interface + /// public interface IFileFormat { - string FileExtension { get; } - string FileExtensionName { get; } - void Load(string fileFullPath); + /// + /// Check if this file is valid to read + /// + /// + bool IsValid(); + + /// + /// Begin encode to an output file + /// + /// Output file + void BeginEncode(string fileFullPath); + + /// + /// Insert a layer image to be encoded + /// + /// + /// + void InsertLayerImageEncode(Image image, uint layerIndex); + + /// + /// Finish the encoding procedure + /// + void EndEncode(); + + /// + /// Decode a slicer file + /// + /// + void Decode(string fileFullPath); + + /// + /// Gets a image from layer + /// + /// The layer index + /// Returns a image + Image GetLayerImage(uint layerIndex); + + /// + /// Get height in mm from layer height + /// + /// The layer height + /// The height in mm float GetHeightFromLayer(uint layerNum); + + /// + /// Clears all definitions and properties, it also dispose valid candidates + /// + void Clear(); + + /// + /// Converts this file type to another file type + /// + /// Target type + /// Output path file + /// True if convert succeed, otherwise false + bool Convert(Type to, string fileFullPath); } } diff --git a/PrusaSL1Reader/ObjectExtensions.cs b/PrusaSL1Reader/ObjectExtensions.cs new file mode 100644 index 0000000..3a20831 --- /dev/null +++ b/PrusaSL1Reader/ObjectExtensions.cs @@ -0,0 +1,24 @@ +/* + * 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.IO; +using System.Runtime.Serialization.Formatters.Binary; + +namespace PrusaSL1Reader +{ + public static class ObjectExtensions + { + public static object DeserializeFromBytes(byte[] bytes) + { + var formatter = new BinaryFormatter(); + using (var stream = new MemoryStream(bytes)) + { + return formatter.Deserialize(stream); + } + } + } +} diff --git a/PrusaSL1Reader/PrusaSL1Reader.csproj b/PrusaSL1Reader/PrusaSL1Reader.csproj index 890a6d5..8fa7e56 100644 --- a/PrusaSL1Reader/PrusaSL1Reader.csproj +++ b/PrusaSL1Reader/PrusaSL1Reader.csproj @@ -7,13 +7,18 @@ https://github.com/sn4k3/PrusaSL1Viewer https://github.com/sn4k3/PrusaSL1Viewer - 0.1.0.0 - 0.1.0.0 - 0.1 + 0.2.0.0 + 0.2.0.0 + 0.2 + + + + + diff --git a/PrusaSL1Reader/SL1File.cs b/PrusaSL1Reader/SL1File.cs index 6275cf3..283de7b 100644 --- a/PrusaSL1Reader/SL1File.cs +++ b/PrusaSL1Reader/SL1File.cs @@ -13,10 +13,13 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; namespace PrusaSL1Reader { - public class SL1File : FileFormat, IDisposable, IEquatable + public class SL1File : FileFormat, IEquatable { #region Sub Classes @@ -265,7 +268,11 @@ namespace PrusaSL1Reader public Statistics Statistics { get; } = new Statistics(); - public List Thumbnails { get; } = new List(2); + public override byte ThumbnailsCount { get; } = 2; + + public override Image[] Thumbnails { get; protected internal set; } + + //public List Thumbnails { get; } = new List(2); public List LayerImages { get; } = new List(); public uint GetLayerCount => (uint)LayerImages.Count; @@ -292,11 +299,6 @@ namespace PrusaSL1Reader return (FileFullPath != null ? FileFullPath.GetHashCode() : 0); } - public void Dispose() - { - Archive?.Dispose(); - } - public override string ToString() { return $"{nameof(FileFullPath)}: {FileFullPath}, {nameof(MaterialSettings)}: {MaterialSettings}, {nameof(PrintSettings)}: {PrintSettings}, {nameof(OutputConfigSettings)}: {OutputConfigSettings}, {nameof(Statistics)}: {Statistics}, {nameof(GetLayerCount)}: {GetLayerCount}, {nameof(TotalHeight)}: {TotalHeight}"; @@ -306,26 +308,42 @@ namespace PrusaSL1Reader #region Contructors public SL1File() { } - public SL1File(string fileFullPath) + public SL1File(string fileFullPath) { - Load(fileFullPath); + Decode(fileFullPath); } #endregion #region Functions - public override string FileExtension { get; } = "sl1"; - public override string FileExtensionName { get; } = "Prusa SL1 Files"; + public override string FileFullPath { get; protected set; } - public override void Load(string fileFullPath) + public override FileExtension[] ValidFiles { get; } = { + new FileExtension("sl1", "Prusa SL1 Files") + }; + + private List> images = new List>(); + + public override void BeginEncode(string fileFullPath) { - FileValidation(fileFullPath); - FileFullPath = fileFullPath; + throw new NotImplementedException(); + } - Archive?.Dispose(); - Thumbnails.Clear(); - LayerImages.Clear(); - Statistics.Clear(); + public override void InsertLayerImageEncode(Image image, uint layerIndex) + { + throw new NotImplementedException(); + } + + public override void EndEncode() + { + throw new NotImplementedException(); + } + + public override void Decode(string fileFullPath) + { + DecodeInternal(fileFullPath); + + FileFullPath = fileFullPath; PrinterSettings = new Printer(); MaterialSettings = new Material(); @@ -343,6 +361,7 @@ namespace PrusaSL1Reader }; Archive = ZipFile.OpenRead(FileFullPath); + byte thumbnailIndex = 0; foreach (ZipArchiveEntry entity in Archive.Entries) { if (entity.Name.EndsWith(".ini")) @@ -383,23 +402,134 @@ namespace PrusaSL1Reader { if (entity.Name.StartsWith("thumbnail")) { - Thumbnails.Add(entity); + using (Stream stream = entity.Open()) + { + Thumbnails[thumbnailIndex] = Image.Load(stream); + stream.Close(); + } + + thumbnailIndex++; + continue; } LayerImages.Add(entity); } } + Statistics.ExecutionTime.Stop(); Debug.WriteLine(Statistics); } + public override Image GetLayerImage(uint layerIndex) + { + return Image.Load(LayerImages[(int)layerIndex].Open()); + } + public override float GetHeightFromLayer(uint layerNum) { return (float)Math.Round(MaterialSettings.InitialLayerHeight + (layerNum - 1) * OutputConfigSettings.LayerHeight, 2); } + public override void Clear() + { + ClearInternal(); + Archive?.Dispose(); + LayerImages.Clear(); + Statistics.Clear(); + } + + public override bool Convert(Type to, string fileFullPath) + { + if (!IsValid()) return false; + + if (to == typeof(CbddlpFile)) + { + CbddlpFile file = new CbddlpFile + { + HeaderSettings = new CbddlpFile.Header + { + Version = 2, + AntiAliasLevel = LookupCustomValue("AntiAliasLevel", 1), + BedSizeX = PrinterSettings.DisplayWidth, + BedSizeY = PrinterSettings.DisplayHeight, + BedSizeZ = PrinterSettings.MaxPrintHeight, + BottomExposureSeconds = MaterialSettings.InitialExposureTime, + BottomLayersCount = PrintSettings.FadedLayers, + BottomLightPWM = LookupCustomValue("BottomLightPWM", 255), // TODO + LayerCount = GetLayerCount, + LayerExposureSeconds = MaterialSettings.ExposureTime, + LayerHeightMilimeter = PrintSettings.LayerHeight, + LayerOffTime = LookupCustomValue("LayerOffTime", 0), // TODO + LightPWM = LookupCustomValue("LightPWM", 255), // TODO + PrintTime = (uint) OutputConfigSettings.PrintTime, + ProjectorType = PrinterSettings.DisplayMirrorX ? 1u : 0u, + ResolutionX = PrinterSettings.DisplayPixelsX, + ResolutionY = PrinterSettings.DisplayPixelsY, + }, + PrintParametersSettings = new CbddlpFile.PrintParameters + { + BottomLayerCount = PrintSettings.FadedLayers, + BottomLiftHeight = LookupCustomValue("BottomLiftHeight", 5), // TODO + BottomLiftSpeed = LookupCustomValue("BottomLiftSpeed", 60), // TODO + BottomLightOffDelay = LookupCustomValue("BottomLightOffDelay", 0), // TODO + CostDollars = OutputConfigSettings.UsedMaterial * MaterialSettings.BottleCost / + MaterialSettings.BottleVolume, + LiftHeight = LookupCustomValue("LiftHeight", 5), // TODO, // TODO + LiftingSpeed = LookupCustomValue("LiftingSpeed", 60), // TODO + LightOffDelay = LookupCustomValue("LightOffDelay", 0), // TODO + RetractSpeed = LookupCustomValue("RetractSpeed", 150), // TODO + VolumeMl = OutputConfigSettings.UsedMaterial, + WeightG = OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity + }, + Thumbnails = Thumbnails, + MachineInfoSettings = new CbddlpFile.MachineInfo() + { + MachineName = PrinterSettings.PrinterSettingsId, + MachineNameSize = (uint)PrinterSettings.PrinterSettingsId.Length + + }, + }; + + + + file.BeginEncode(fileFullPath); + + for (uint layerIndex = 0; layerIndex < GetLayerCount; layerIndex++) + { + file.InsertLayerImageEncode(GetLayerImage(layerIndex), layerIndex); + } + + file.EndEncode(); + } + + return false; + } + + public T LookupCustomValue(string name, T defaultValue) + { + string result = string.Empty; + name += '_'; + + int index = PrinterSettings.PrinterNotes.IndexOf(name, StringComparison.Ordinal); + int startIndex = index + name.Length; + + if (index < 0 || PrinterSettings.PrinterNotes.Length < startIndex) return defaultValue; + for (int i = startIndex; i < PrinterSettings.PrinterNotes.Length; i++) + { + char c = PrinterSettings.PrinterNotes[i]; + if (!Char.IsLetterOrDigit(c) && c != '.') + { + break; + } + + result += PrinterSettings.PrinterNotes[i]; + } + + return result.Convert(); + } + #endregion #region Static Functions diff --git a/PrusaSL1Reader/StringExtensions.cs b/PrusaSL1Reader/StringExtensions.cs index c28612c..1d042f5 100644 --- a/PrusaSL1Reader/StringExtensions.cs +++ b/PrusaSL1Reader/StringExtensions.cs @@ -13,6 +13,11 @@ namespace PrusaSL1Reader { public static class StringExtensions { + /// + /// Upper the first character in a string + /// + /// Input string + /// Modified string with fist character upper public static string FirstCharToUpper(this string input) { switch (input) @@ -23,6 +28,12 @@ namespace PrusaSL1Reader } } + /// + /// Converts a string into a target type + /// + /// Target type to convert into + /// Value + /// Converted value into target type public static T Convert(this string input) { var converter = TypeDescriptor.GetConverter(typeof(T)); diff --git a/PrusaSL1Viewer/App.config b/PrusaSL1Viewer/App.config index 4bfa005..d896ff5 100644 --- a/PrusaSL1Viewer/App.config +++ b/PrusaSL1Viewer/App.config @@ -1,6 +1,26 @@ - + - + + + + + + + + + + + + + + + + + + + + + diff --git a/PrusaSL1Viewer/FrmMain.Designer.cs b/PrusaSL1Viewer/FrmMain.Designer.cs index c837554..660b61d 100644 --- a/PrusaSL1Viewer/FrmMain.Designer.cs +++ b/PrusaSL1Viewer/FrmMain.Designer.cs @@ -56,6 +56,7 @@ this.scCenter = new System.Windows.Forms.SplitContainer(); this.pbLayer = new System.Windows.Forms.PictureBox(); this.pbLayers = new System.Windows.Forms.ProgressBar(); + this.menuEditConvert = new System.Windows.Forms.ToolStripMenuItem(); this.menu.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); @@ -116,7 +117,8 @@ // menuEdit // this.menuEdit.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuEditExtract}); + this.menuEditExtract, + this.menuEditConvert}); this.menuEdit.Enabled = false; this.menuEdit.Name = "menuEdit"; this.menuEdit.Size = new System.Drawing.Size(39, 20); @@ -128,7 +130,7 @@ this.menuEditExtract.Image = global::PrusaSL1Viewer.Properties.Resources.Extract_object_16x16; this.menuEditExtract.Name = "menuEditExtract"; this.menuEditExtract.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.E))); - this.menuEditExtract.Size = new System.Drawing.Size(150, 22); + this.menuEditExtract.Size = new System.Drawing.Size(181, 22); this.menuEditExtract.Text = "&Extract"; this.menuEditExtract.Click += new System.EventHandler(this.MenuItemClicked); // @@ -349,6 +351,15 @@ this.pbLayers.Step = 1; this.pbLayers.TabIndex = 6; // + // menuEditConvert + // + this.menuEditConvert.Name = "menuEditConvert"; + this.menuEditConvert.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Alt) + | System.Windows.Forms.Keys.C))); + this.menuEditConvert.Size = new System.Drawing.Size(181, 22); + this.menuEditConvert.Text = "&Convert"; + this.menuEditConvert.Click += new System.EventHandler(this.MenuItemClicked); + // // FrmMain // this.AllowDrop = true; @@ -410,6 +421,7 @@ private System.Windows.Forms.SplitContainer scCenter; private System.Windows.Forms.PictureBox pbLayer; private System.Windows.Forms.ProgressBar pbLayers; + private System.Windows.Forms.ToolStripMenuItem menuEditConvert; } } diff --git a/PrusaSL1Viewer/FrmMain.cs b/PrusaSL1Viewer/FrmMain.cs index a7ff2c1..0b7d3de 100644 --- a/PrusaSL1Viewer/FrmMain.cs +++ b/PrusaSL1Viewer/FrmMain.cs @@ -13,6 +13,7 @@ using System.IO.Compression; using System.Reflection; using System.Windows.Forms; using PrusaSL1Reader; +using SixLabors.ImageSharp.Processing; namespace PrusaSL1Viewer { @@ -34,7 +35,7 @@ namespace PrusaSL1Viewer #region Events private void sbLayers_ValueChanged(object sender, EventArgs e) { - ShowLayer(sbLayers.Maximum - sbLayers.Value); + ShowLayer((uint)(sbLayers.Maximum - sbLayers.Value)); } @@ -46,7 +47,7 @@ namespace PrusaSL1Viewer using (OpenFileDialog openFile = new OpenFileDialog()) { openFile.CheckFileExists = true; - openFile.Filter = $@"{Program.SL1File.FileExtensionName} (*.{Program.SL1File.FileExtension})|*.{Program.SL1File.FileExtension}"; + openFile.Filter = Program.SL1File.GetFileFilter(); openFile.FilterIndex = 0; if (openFile.ShowDialog() == DialogResult.OK) { @@ -97,13 +98,34 @@ namespace PrusaSL1Viewer return; } + if (ReferenceEquals(sender, menuEditConvert)) + { + using (SaveFileDialog dialog = new SaveFileDialog()) + { + dialog.FileName = Path.GetFileNameWithoutExtension(Program.SL1File.FileFullPath); + using (CbddlpFile file = new CbddlpFile()) + { + dialog.Filter = file.GetFileFilter(); + } + + + if (dialog.ShowDialog() == DialogResult.OK) + { + Program.SL1File.Convert(typeof(CbddlpFile), dialog.FileName); + } + + } + + return; + } + if (ReferenceEquals(sender, menuAboutAbout)) { Program.FrmAbout.ShowDialog(); } if (ReferenceEquals(sender, menuAboutWebsite)) { - Process.Start(PrusaSL1Reader.About.Website); + Process.Start(About.Website); return; } @@ -124,7 +146,7 @@ namespace PrusaSL1Viewer if (ReferenceEquals(files, null)) return; foreach (string file in files) { - if (!file.EndsWith($".{Program.SL1File.FileExtension}")) continue; + if (!Program.SL1File.IsExtensionValid(file, true)) continue; try { ProcessFile(file); @@ -141,14 +163,14 @@ namespace PrusaSL1Viewer void ProcessFile(string fileName) { - if (!ReferenceEquals(Program.SL1File, null)) + /* if (!ReferenceEquals(Program.SL1File, null)) { Program.SL1File.Dispose(); - } + }*/ - Program.SL1File.Load(fileName); + Program.SL1File.Decode(fileName); - pbThumbnail.Image = Image.FromStream(Program.SL1File.Thumbnails[0].Open()); + pbThumbnail.Image = Program.SL1File.Thumbnails[0].ToBitmap(); //ShowLayer(0); sbLayers.SmallChange = 1; @@ -190,12 +212,15 @@ namespace PrusaSL1Viewer Text = $"{FrmAbout.AssemblyTitle} Version: {FrmAbout.AssemblyVersion} File: {Path.GetFileName(fileName)}"; } - void ShowLayer(int layerNum) + void ShowLayer(uint layerNum) { //if(!ReferenceEquals(pbLayer.Image, null)) // pbLayer.Image.Dispose(); SLOW! LET GC DO IT - pbLayer.Image = Image.FromStream(Program.SL1File.LayerImages[layerNum].Open()); - pbLayer.Image.RotateFlip(RotateFlipType.Rotate90FlipNone); + //pbLayer.Image = Image.FromStream(Program.SL1File.LayerImages[layerNum].Open()); + //pbLayer.Image.RotateFlip(RotateFlipType.Rotate90FlipNone); + var image = Program.SL1File.GetLayerImage(layerNum); + image.Mutate(x => x.Rotate(RotateMode.Rotate90)); + pbLayer.Image = image.ToBitmap(); byte percent = (byte)((layerNum + 1) * 100 / Program.SL1File.GetLayerCount); diff --git a/PrusaSL1Viewer/ImageSharpExtensions.cs b/PrusaSL1Viewer/ImageSharpExtensions.cs new file mode 100644 index 0000000..4366112 --- /dev/null +++ b/PrusaSL1Viewer/ImageSharpExtensions.cs @@ -0,0 +1,36 @@ +using System.IO; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; + +namespace PrusaSL1Viewer +{ + public static class ImageSharpExtensions + { + public static System.Drawing.Bitmap ToBitmap(this Image image) where TPixel : struct, IPixel + { + using (var memoryStream = new MemoryStream()) + { + var imageEncoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance); + image.Save(memoryStream, imageEncoder); + + memoryStream.Seek(0, SeekOrigin.Begin); + + return new System.Drawing.Bitmap(memoryStream); + } + } + + public static Image ToImageSharpImage(this System.Drawing.Bitmap bitmap) where TPixel : struct, IPixel + { + using (var memoryStream = new MemoryStream()) + { + bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png); + + memoryStream.Seek(0, SeekOrigin.Begin); + + return Image.Load(memoryStream); + } + } + } +} diff --git a/PrusaSL1Viewer/Program.cs b/PrusaSL1Viewer/Program.cs index 7a02bff..e789378 100644 --- a/PrusaSL1Viewer/Program.cs +++ b/PrusaSL1Viewer/Program.cs @@ -12,6 +12,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using PrusaSL1Reader; +using SixLabors.ImageSharp; namespace PrusaSL1Viewer { @@ -33,11 +34,14 @@ namespace PrusaSL1Viewer FrmAbout = new FrmAbout(); Application.Run(FrmMain); - //CbddlpFile file = new CbddlpFile(); - - //file.Load(@"D:\Tiago\Desktop\_Coronavirus-v6-HIRES-Supports.cbddlp"); - + CbddlpFile file = new CbddlpFile(); + //file.Decode(@"D:\Tiago\Desktop\_Coronavirus-v6-HIRES-Supports_NOAA.cbddlp"); + //file.Decode(@"D:\Tiago\Desktop\coronanew11.cbddlp"); + /*file.GetLayerImage(0).Save(@"D:\img0.png"); + file.GetLayerImage(10).Save(@"D:\img10.png"); + file.GetLayerImage(20).Save(@"D:\img20.png"); + file.GetLayerImage(50).Save(@"D:\img50.png");*/ //LayerPositionZ: 0, LayerExposure: 35, LayerOffTimeSeconds: 0, DataAddress: 283641, DataSize: 30804, Unknown1: 0, Unknown2: 0, Unknown3: 0, Unknown4: 0 } diff --git a/PrusaSL1Viewer/Properties/AssemblyInfo.cs b/PrusaSL1Viewer/Properties/AssemblyInfo.cs index 9005714..4d42de3 100644 --- a/PrusaSL1Viewer/Properties/AssemblyInfo.cs +++ b/PrusaSL1Viewer/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.1.0.0")] -[assembly: AssemblyFileVersion("0.1.0.0")] +[assembly: AssemblyVersion("0.2.0.0")] +[assembly: AssemblyFileVersion("0.2.0.0")] diff --git a/PrusaSL1Viewer/PrusaSL1Viewer.csproj b/PrusaSL1Viewer/PrusaSL1Viewer.csproj index 7d48556..0c3c766 100644 --- a/PrusaSL1Viewer/PrusaSL1Viewer.csproj +++ b/PrusaSL1Viewer/PrusaSL1Viewer.csproj @@ -1,5 +1,6 @@  + Debug @@ -28,6 +29,8 @@ false false true + + AnyCPU @@ -54,13 +57,71 @@ PrusaSL1Viewer.ico + + ..\packages\BinarySerializer.8.5.1\lib\net46\BinarySerializer.dll + + + ..\packages\SixLabors.Core.1.0.0-beta0008\lib\netstandard2.0\SixLabors.Core.dll + + + ..\packages\SixLabors.ImageSharp.1.0.0-beta0007\lib\net472\SixLabors.ImageSharp.dll + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + True + + + ..\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll + True + True + + + ..\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll + True + True + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll + True + True + + + ..\packages\System.Reflection.TypeExtensions.4.7.0\lib\net461\System.Reflection.TypeExtensions.dll + + + ..\packages\System.Runtime.4.3.1\lib\net462\System.Runtime.dll + True + True + + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0-preview.2.20160.6\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll + True + True + + + @@ -77,6 +138,7 @@ FrmMain.cs + @@ -104,6 +166,7 @@ README.md + SettingsSingleFileGenerator Settings.Designer.cs @@ -147,4 +210,10 @@ + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/PrusaSL1Viewer/packages.config b/PrusaSL1Viewer/packages.config new file mode 100644 index 0000000..ba4b62b --- /dev/null +++ b/PrusaSL1Viewer/packages.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file