mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
2c7ad09dc0
- (Change) `Layer.IsBottomLayer` no longer calculate the value using the position of the layer, a new property `IsBottomLayerByHeight` is now used to get that result - (Improvement) Tool - Double exposure: Increase the bottom layer count per cloned bottom layer - (Improvement) Calibration - Exposure time finder: Set the absolute bottom layer count accordingly when also testing for bottom time - (Improvement) Goo: Enforce Wait times or Light-off-delay flag based on property set - (Fix) AnyCubic and Goo: `PerLayerSetting` flag was set inverted causing printer not to follow layer settings when it should and also the otherwise (#689) - (Fix) Tool - Scripting: Prevent from reload UI multiple times when using profiles (#694)
1081 lines
45 KiB
C#
1081 lines
45 KiB
C#
using BinarySerialization;
|
||
using Emgu.CV;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.Drawing;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using UVtools.Core.Exceptions;
|
||
using UVtools.Core.Extensions;
|
||
using UVtools.Core.Layers;
|
||
using UVtools.Core.Operations;
|
||
|
||
namespace UVtools.Core.FileFormats;
|
||
|
||
public sealed class GooFile : FileFormat
|
||
{
|
||
|
||
#region Constants
|
||
|
||
private const string FileVersion = "V3.0";
|
||
|
||
private static readonly byte[] FileMagic = {
|
||
0x07, 0x00, 0x00, 0x00,
|
||
0x44, 0x4C, 0x50, 0x00
|
||
};
|
||
|
||
private static byte[] Delimiter => new byte[]{ 0x0D, 0x0A };
|
||
|
||
private static byte LayerMagic => 0x55;
|
||
|
||
#endregion
|
||
|
||
#region Enums
|
||
public enum DelayModes : byte
|
||
{
|
||
/// <summary>
|
||
/// Time with motor movement
|
||
/// </summary>
|
||
LightOff = 0,
|
||
|
||
/// <summary>
|
||
/// Absolute time to wait
|
||
/// </summary>
|
||
WaitTime = 1
|
||
}
|
||
#endregion
|
||
|
||
#region Sub Classes
|
||
|
||
public class FileHeader
|
||
{
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(0)] [FieldLength(4)] public string Version { get; set; } = FileVersion;
|
||
[FieldOrder(1)] [FieldCount(8)] public byte[] Magic { get; set; } = FileMagic;
|
||
[FieldOrder(2)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string SoftwareName { get; set; } = About.Software;
|
||
[FieldOrder(3)] [FieldLength(24)] [SerializeAs(SerializedType.TerminatedString)] public string SoftwareVersion { get; set; } = About.VersionStr;
|
||
[FieldOrder(4)] [FieldLength(24)] [SerializeAs(SerializedType.TerminatedString)] public string FileCreateTime { get; set; } = DateTime.UtcNow.ToString("yyyy-mm-dd hh:mm:ss");
|
||
[FieldOrder(5)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string MachineName { get; set; } = DefaultMachineName;
|
||
[FieldOrder(6)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string MachineType { get; set; } = "DLP";
|
||
[FieldOrder(7)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string ProfileName { get; set; } = About.Software;
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(8)] public ushort AntiAliasingLevel { get; set; } = 8;
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(9)] public ushort GreyLevel { get; set; } = 1;
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(10)] public ushort BlurLevel { get; set; } = 0;
|
||
[FieldOrder(11)] [FieldCount(116 * 116 * 2)] public byte[] SmallPreview565 { get; set; } = Array.Empty<byte>();
|
||
[FieldOrder(12)] [FieldCount(2)] public byte[] SmallPreviewDelimiter { get; set; } = Delimiter;
|
||
[FieldOrder(13)] [FieldCount(290 * 290 * 2)] public byte[] BigPreview565 { get; set; } = Array.Empty<byte>();
|
||
[FieldOrder(14)] [FieldCount(2)] public byte[] BigPreviewDelimiter { get; set; } = Delimiter;
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(15)] public uint LayerCount { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(16)] public ushort ResolutionX { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(17)] public ushort ResolutionY { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(18)] public bool MirrorX { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(19)] public bool MirrorY { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(20)] public float DisplayWidth { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(21)] public float DisplayHeight { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(22)] public float MachineZ { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(23)] public float LayerHeight { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(24)] public float ExposureTime { get; set; }
|
||
/// <summary>
|
||
/// 0: Light off delay mode | 1:Wait time mode
|
||
/// </summary>
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(25)] public DelayModes DelayMode { get; set; } = DelayModes.WaitTime;
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(26)] public float LightOffDelay { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(27)] public float BottomWaitTimeAfterCure { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(28)] public float BottomWaitTimeAfterLift { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(29)] public float BottomWaitTimeBeforeCure { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(30)] public float WaitTimeAfterCure { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(31)] public float WaitTimeAfterLift { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(32)] public float WaitTimeBeforeCure { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(33)] public float BottomExposureTime { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(34)] public uint BottomLayerCount { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(35)] public float BottomLiftHeight { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(36)] public float BottomLiftSpeed { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(37)] public float LiftHeight { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(38)] public float LiftSpeed { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(39)] public float BottomRetractHeight { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(40)] public float BottomRetractSpeed { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(41)] public float RetractHeight { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(42)] public float RetractSpeed { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(43)] public float BottomLiftHeight2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(44)] public float BottomLiftSpeed2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(45)] public float LiftHeight2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(46)] public float LiftSpeed2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(47)] public float BottomRetractHeight2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(48)] public float BottomRetractSpeed2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(49)] public float RetractHeight2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(50)] public float RetractSpeed2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(51)] public ushort BottomLightPWM { get; set; } = DefaultBottomLightPWM;
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(52)] public ushort LightPWM { get; set; } = DefaultLightPWM;
|
||
/// <summary>
|
||
/// <para>0: Normal mode</para>
|
||
/// <para>1: Advance mode, printing use the value of "Layer Definition Content"</para>
|
||
/// </summary>
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(53)] public bool PerLayerSettings { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(54)] public uint PrintTime { get; set; }
|
||
/// <summary>
|
||
/// // The volume of all parts. unit: mm3
|
||
/// </summary>
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(55)] public float Volume { get; set; }
|
||
/// <summary>
|
||
/// The weight of all parts. unit: g
|
||
/// </summary>
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(56)] public float MaterialGrams { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(57)] public float MaterialCost { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(58)] [FieldLength(8)] [SerializeAs(SerializedType.TerminatedString)] public string PriceCurrencySymbol { get; set; } = "$";
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(59)] public uint LayerDefAddress { get; set; } // 195477
|
||
/// <summary>
|
||
/// <para>0:The range of pixel's gray value is from 0x0 ~ 0xf</para>
|
||
/// <para>1:The range of pixel's gray value is from 0x0 ~ 0xff</para>
|
||
/// </summary>
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(60)] public byte GrayScaleLevel { get; set; } = 1;
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(61)] public ushort TransitionLayerCount { get; set; }
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"{nameof(Version)}: {Version}, {nameof(Magic)}: {Magic}, {nameof(SoftwareName)}: {SoftwareName}, {nameof(SoftwareVersion)}: {SoftwareVersion}, {nameof(FileCreateTime)}: {FileCreateTime}, {nameof(MachineName)}: {MachineName}, {nameof(MachineType)}: {MachineType}, {nameof(ProfileName)}: {ProfileName}, {nameof(AntiAliasingLevel)}: {AntiAliasingLevel}, {nameof(GreyLevel)}: {GreyLevel}, {nameof(BlurLevel)}: {BlurLevel}, {nameof(SmallPreview565)}: {SmallPreview565}, {nameof(SmallPreviewDelimiter)}: {SmallPreviewDelimiter}, {nameof(BigPreview565)}: {BigPreview565}, {nameof(BigPreviewDelimiter)}: {BigPreviewDelimiter}, {nameof(LayerCount)}: {LayerCount}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(MirrorX)}: {MirrorX}, {nameof(MirrorY)}: {MirrorY}, {nameof(DisplayWidth)}: {DisplayWidth}, {nameof(DisplayHeight)}: {DisplayHeight}, {nameof(MachineZ)}: {MachineZ}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(DelayMode)}: {DelayMode}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(BottomWaitTimeAfterCure)}: {BottomWaitTimeAfterCure}, {nameof(BottomWaitTimeAfterLift)}: {BottomWaitTimeAfterLift}, {nameof(BottomWaitTimeBeforeCure)}: {BottomWaitTimeBeforeCure}, {nameof(WaitTimeAfterCure)}: {WaitTimeAfterCure}, {nameof(WaitTimeAfterLift)}: {WaitTimeAfterLift}, {nameof(WaitTimeBeforeCure)}: {WaitTimeBeforeCure}, {nameof(BottomExposureTime)}: {BottomExposureTime}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(BottomRetractHeight)}: {BottomRetractHeight}, {nameof(BottomRetractSpeed)}: {BottomRetractSpeed}, {nameof(RetractHeight)}: {RetractHeight}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(BottomLiftHeight2)}: {BottomLiftHeight2}, {nameof(BottomLiftSpeed2)}: {BottomLiftSpeed2}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(BottomRetractHeight2)}: {BottomRetractHeight2}, {nameof(BottomRetractSpeed2)}: {BottomRetractSpeed2}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(LightPWM)}: {LightPWM}, {nameof(PerLayerSettings)}: {PerLayerSettings}, {nameof(PrintTime)}: {PrintTime}, {nameof(Volume)}: {Volume}, {nameof(MaterialGrams)}: {MaterialGrams}, {nameof(MaterialCost)}: {MaterialCost}, {nameof(PriceCurrencySymbol)}: {PriceCurrencySymbol}, {nameof(LayerDefAddress)}: {LayerDefAddress}, {nameof(GrayScaleLevel)}: {GrayScaleLevel}, {nameof(TransitionLayerCount)}: {TransitionLayerCount}";
|
||
}
|
||
}
|
||
|
||
public class LayerDef
|
||
{
|
||
/// <summary>
|
||
/// 0: reserve
|
||
/// 1: current layer pause printing
|
||
/// </summary>
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(0)] public ushort Pause { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(1)] public float PausePositionZ { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(2)] public float PositionZ { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(3)] public float ExposureTime { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(4)] public float LightOffDelay { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(5)] public float WaitTimeAfterCure { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(6)] public float WaitTimeAfterLift { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(7)] public float WaitTimeBeforeCure { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(8)] public float LiftHeight { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(9)] public float LiftSpeed { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(10)] public float LiftHeight2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(11)] public float LiftSpeed2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(12)] public float RetractHeight { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(13)] public float RetractSpeed { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(14)] public float RetractHeight2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(15)] public float RetractSpeed2 { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(16)] public ushort LightPWM { get; set; }
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(17)] [FieldCount(2)] public byte[] DelimiterData { get; set; } = Delimiter;
|
||
[FieldEndianness(Endianness.Big)] [FieldOrder(18)] public uint DataLength { get; set; }
|
||
|
||
|
||
[Ignore] public GooFile? Parent { get; set; }
|
||
|
||
[Ignore] public byte[] EncodedRle { get; set; } = Array.Empty<byte>();
|
||
|
||
// DelimiterRLE
|
||
|
||
public LayerDef() { }
|
||
|
||
public LayerDef(GooFile parent, Layer layer)
|
||
{
|
||
Parent = parent;
|
||
PausePositionZ = parent.MachineZ;
|
||
SetFrom(layer);
|
||
}
|
||
|
||
public void SetFrom(Layer layer)
|
||
{
|
||
PositionZ = layer.PositionZ;
|
||
ExposureTime = layer.ExposureTime;
|
||
LightOffDelay = layer.LightOffDelay;
|
||
LiftHeight = layer.LiftHeight;
|
||
LiftSpeed = layer.LiftSpeed;
|
||
LiftHeight2 = layer.LiftHeight2;
|
||
LiftSpeed2 = layer.LiftSpeed2;
|
||
RetractSpeed = layer.RetractSpeed;
|
||
RetractHeight2 = layer.RetractHeight2;
|
||
RetractSpeed2 = layer.RetractSpeed2;
|
||
WaitTimeAfterCure = layer.WaitTimeAfterCure;
|
||
WaitTimeAfterLift = layer.WaitTimeAfterLift;
|
||
WaitTimeBeforeCure = layer.WaitTimeBeforeCure;
|
||
LightPWM = layer.LightPWM;
|
||
}
|
||
|
||
public void CopyTo(Layer layer)
|
||
{
|
||
layer.PositionZ = PositionZ;
|
||
layer.ExposureTime = ExposureTime;
|
||
layer.LightOffDelay = LightOffDelay;
|
||
layer.LiftHeight = LiftHeight;
|
||
layer.LiftSpeed = LiftSpeed;
|
||
layer.LiftHeight2 = LiftHeight2;
|
||
layer.LiftSpeed2 = LiftSpeed2;
|
||
layer.RetractSpeed = RetractSpeed;
|
||
layer.RetractHeight2 = RetractHeight2;
|
||
layer.RetractSpeed2 = RetractSpeed2;
|
||
layer.WaitTimeAfterCure = WaitTimeAfterCure;
|
||
layer.WaitTimeAfterLift = WaitTimeAfterLift;
|
||
layer.WaitTimeBeforeCure = WaitTimeBeforeCure;
|
||
layer.LightPWM = (byte)LightPWM;
|
||
}
|
||
|
||
|
||
public Mat DecodeImage(uint layerIndex, bool consumeRle = true)
|
||
{
|
||
var mat = EmguExtensions.InitMat(Parent!.Resolution);
|
||
|
||
if (DataLength <= 3) return mat;
|
||
|
||
if (EncodedRle![0] != LayerMagic) throw new MessageException($"RLE for layer {layerIndex} is corrupted, should start with {LayerMagic} but got {EncodedRle[0]}");
|
||
|
||
int pixel = 0;
|
||
var lastByteIndex = DataLength - 1;
|
||
byte color = 0;
|
||
byte checkSum = 0;
|
||
|
||
for (var i = 1; i < lastByteIndex; i++)
|
||
{
|
||
// Calculate checksum
|
||
unchecked
|
||
{
|
||
checkSum += EncodedRle[i];
|
||
}
|
||
}
|
||
checkSum = (byte)~checkSum;
|
||
if (EncodedRle[^1] != checkSum) throw new MessageException($"Decoded RLE for layer {layerIndex} is corrupted, expected checksum <{EncodedRle[^1]}>, got <{checkSum}>");
|
||
|
||
for (var i = 1; i < lastByteIndex; i++)
|
||
{
|
||
/* Byte0[7:6]: The type of chunk
|
||
* (0x0) 0 0 This chunk contain all 0x0 pixels
|
||
* (0x1) 0 1 This chunk contain the value of gray between 0x1 to 0xfe. The gray value is after byte0.
|
||
* (0x2) 1 0 This chunk contain the diff value from the previous pixel
|
||
* (0x3) 1 1 This chunk contain all 0xff pixels
|
||
*/
|
||
byte chunkType = (byte)(EncodedRle[i] >> 6);
|
||
int stride = 0;
|
||
|
||
int strideIndex0 = i;
|
||
int strideIndex1 = i + 1;
|
||
int strideIndex2 = i + 2;
|
||
int strideIndex3 = i + 3;
|
||
|
||
if (chunkType == 0x0) // 0 0
|
||
{
|
||
color = byte.MinValue;
|
||
}
|
||
else if (chunkType == 0x1) // 0 1
|
||
{
|
||
color = EncodedRle[++i];
|
||
strideIndex1++;
|
||
strideIndex2++;
|
||
strideIndex3++;
|
||
}
|
||
else if (chunkType == 0x2) // 1 0
|
||
{
|
||
/* When byte0[7:6] is [1:0], the meaning of byte0[5:4] follow below definition:
|
||
* 0 0 byte0[3:0] is the positive diff value. that's mean
|
||
current value subtract previous value is bigger
|
||
than 0. The range is from 0 to 15. 0x0 map to 0.
|
||
0xf map to 15.
|
||
* 0 1 byte0[3:0] is the positive diff value. And this
|
||
value's run-length represent by byte1[7:0]
|
||
* 1 0 byte0[3:0] is the negative diff value.that's mean
|
||
current value subtract previous value is smaller
|
||
than 0. The range is from 0 to 15. 0x0 map to 0.
|
||
0xf map to 15.
|
||
* 1 1 byte0[3:0] is the negative diff value. And this
|
||
value's run-length represent by byte1[7:0]
|
||
*/
|
||
byte diffType = (byte)(EncodedRle[i] >> 4 & 0x3);
|
||
byte diffValue = (byte)(EncodedRle[i] & 0xf);
|
||
if (diffType == 0x0)
|
||
{
|
||
color += diffValue;
|
||
stride = 1;
|
||
}
|
||
else if (diffType == 0x1)
|
||
{
|
||
color += diffValue;
|
||
stride = EncodedRle[++i];
|
||
}
|
||
else if (diffType == 0x2)
|
||
{
|
||
color -= diffValue;
|
||
stride = 1;
|
||
}
|
||
else if (diffType == 0x3)
|
||
{
|
||
color -= diffValue;
|
||
stride = EncodedRle[++i];
|
||
}
|
||
else
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(diffType), $"Diff type {diffType:X} is out of range, can only go up to 0x3.");
|
||
}
|
||
}
|
||
else if (chunkType == 0x3) // 1 1
|
||
{
|
||
color = byte.MaxValue;
|
||
}
|
||
else
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(chunkType), $"Chunk type {chunkType:X} is out of range, can only go up to 0x3.");
|
||
}
|
||
|
||
if (chunkType != 0x2)
|
||
{
|
||
/* Byte0[5:4]: The length of chunk except when byte0[7:6] is [1 0]
|
||
* (0x0) 0 0 4-bit run-length use byte0[3:0]
|
||
* (0x1) 0 1 The run-length consist by byte1[7:0] and byte0[3:0]
|
||
* (0x2) 1 0 The run-length consist by byte1[7:0], byte2[7:0] and byte0[3:0]
|
||
* (0x3) 1 1 The run-length consist by byte1[7:0], byte2[7:0], byte3[7:0] and byte0[3:0]
|
||
*/
|
||
byte chunkLength = (byte) (EncodedRle[strideIndex0] >> 4 & 0x3);
|
||
switch (chunkLength)
|
||
{
|
||
case 0x0:
|
||
stride = EncodedRle[strideIndex0] & 0xF;
|
||
break;
|
||
case 0x1:
|
||
stride = (EncodedRle[strideIndex1] << 4) + (EncodedRle[strideIndex0] & 0xF);
|
||
i += 1;
|
||
break;
|
||
case 0x2:
|
||
stride = (EncodedRle[strideIndex1] << 12) + (EncodedRle[strideIndex2] << 4) + (EncodedRle[strideIndex0] & 0xF);
|
||
i += 2;
|
||
break;
|
||
case 0x3:
|
||
stride = (EncodedRle[strideIndex1] << 20) + (EncodedRle[strideIndex2] << 12) + (EncodedRle[strideIndex3] << 4) + (EncodedRle[strideIndex0] & 0xF);
|
||
i += 3;
|
||
break;
|
||
default:
|
||
throw new ArgumentOutOfRangeException(nameof(chunkLength), $"Chunk length {chunkLength:X} is out of range, can only go up to 0x3.");
|
||
}
|
||
}
|
||
|
||
mat.FillSpan(ref pixel, stride, color);
|
||
}
|
||
|
||
if (consumeRle) EncodedRle = null!;
|
||
|
||
return mat;
|
||
}
|
||
|
||
public byte[] EncodeImage(Mat image, uint layerIndex)
|
||
{
|
||
List<byte> rle = new(){ LayerMagic };
|
||
byte previousColor = 0;
|
||
byte currentColor = 0;
|
||
uint stride = 0;
|
||
byte checkSum = 0;
|
||
var span = image.GetDataByteSpan();
|
||
|
||
void AddRep()
|
||
{
|
||
if (stride == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
int firstByteIndex = rle.Count;
|
||
rle.Add(0);
|
||
|
||
// Difference mode
|
||
var differenceColor = (byte) Math.Abs(currentColor - previousColor);
|
||
if (stride <= byte.MaxValue && differenceColor <= 0xF)
|
||
{
|
||
rle[firstByteIndex] = (byte) (0x2 << 6 | (differenceColor & 0xF));
|
||
if (stride > 1)
|
||
{
|
||
rle[firstByteIndex] |= 0x1 << 4;
|
||
}
|
||
|
||
if (previousColor > currentColor)
|
||
{
|
||
rle[firstByteIndex] |= 0x1 << 5;
|
||
}
|
||
|
||
if (stride > 1)
|
||
{
|
||
rle.Add((byte) stride);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/*if (currentColor == byte.MinValue)
|
||
{
|
||
0 0 This chunk contain all 0x0 pixels
|
||
firstByte |= 0x00 << 6;
|
||
}*/
|
||
if (currentColor == byte.MaxValue)
|
||
{
|
||
// 1 1 This chunk contain all 0xff pixels
|
||
rle[firstByteIndex] |= 0x3 << 6;
|
||
}
|
||
else if (currentColor > byte.MinValue)
|
||
{
|
||
// 0 1 This chunk contain the value of gray between 0x1 to 0xfe. The gray value is after byte0.
|
||
rle[firstByteIndex] |= 0x1 << 6;
|
||
rle.Add(currentColor);
|
||
}
|
||
|
||
rle[firstByteIndex] |= (byte) (stride & 0xF);
|
||
if (stride <= 0xF)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (stride <= 0xFFF)
|
||
{
|
||
rle[firstByteIndex] |= 0x1 << 4;
|
||
rle.Add((byte) (stride >> 4));
|
||
return;
|
||
}
|
||
|
||
if (stride <= 0xFFFFF)
|
||
{
|
||
rle[firstByteIndex] |= 0x2 << 4;
|
||
rle.Add((byte) (stride >> 12));
|
||
rle.Add((byte) (stride >> 4));
|
||
return;
|
||
}
|
||
|
||
if (stride <= 0xFFFFFFF)
|
||
{
|
||
rle[firstByteIndex] |= 0x3 << 4;
|
||
rle.Add((byte) (stride >> 20));
|
||
rle.Add((byte) (stride >> 12));
|
||
rle.Add((byte) (stride >> 4));
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < span.Length; i++)
|
||
{
|
||
if (currentColor == span[i])
|
||
{
|
||
stride++;
|
||
}
|
||
else
|
||
{
|
||
AddRep();
|
||
stride = 1;
|
||
previousColor = currentColor;
|
||
currentColor = span[i];
|
||
}
|
||
}
|
||
|
||
AddRep();
|
||
|
||
// Calculate checksum
|
||
for (int i = 1; i < rle.Count; i++)
|
||
{
|
||
unchecked
|
||
{
|
||
checkSum += rle[i];
|
||
}
|
||
}
|
||
|
||
rle.Add((byte) ~checkSum);
|
||
|
||
EncodedRle = rle.ToArray();
|
||
DataLength = (uint)EncodedRle.Length;
|
||
|
||
return EncodedRle;
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"{nameof(Pause)}: {Pause}, {nameof(PausePositionZ)}: {PausePositionZ}, {nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(WaitTimeAfterCure)}: {WaitTimeAfterCure}, {nameof(WaitTimeAfterLift)}: {WaitTimeAfterLift}, {nameof(WaitTimeBeforeCure)}: {WaitTimeBeforeCure}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(RetractHeight)}: {RetractHeight}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}, {nameof(LightPWM)}: {LightPWM}, {nameof(DelimiterData)}: {DelimiterData}, {nameof(DataLength)}: {DataLength}, {nameof(Parent)}: {Parent}, {nameof(EncodedRle)}: {EncodedRle}";
|
||
}
|
||
}
|
||
|
||
public class FileFooter
|
||
{
|
||
[FieldOrder(0)] public byte Padding1 { get; set; }
|
||
[FieldOrder(1)] public byte Padding2 { get; set; }
|
||
[FieldOrder(2)] public byte Padding3 { get; set; }
|
||
[FieldOrder(3)] [FieldCount(8)] public byte[] Magic { get; set; } = FileMagic;
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"{nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(Padding3)}: {Padding3}, {nameof(Magic)}: {Magic}";
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
public override FileFormatType FileType => FileFormatType.Binary;
|
||
|
||
public override FileExtension[] FileExtensions { get; } = {
|
||
new(typeof(GooFile), "goo", "Elegoo GOO"),
|
||
};
|
||
|
||
public override Size[]? ThumbnailsOriginalSize { get; } =
|
||
{
|
||
new(116, 116),
|
||
new(290, 290)
|
||
};
|
||
|
||
public FileHeader Header { get; private set; } = new();
|
||
|
||
public LayerDef[]? LayersDefinition { get; private set; }
|
||
|
||
public FileFooter Footer { get; private set; } = new();
|
||
|
||
public override PrintParameterModifier[]? PrintParameterModifiers { get; } = {
|
||
PrintParameterModifier.BottomLayerCount,
|
||
PrintParameterModifier.TransitionLayerCount,
|
||
|
||
//PrintParameterModifier.BottomLightOffDelay,
|
||
PrintParameterModifier.LightOffDelay,
|
||
|
||
PrintParameterModifier.BottomWaitTimeBeforeCure,
|
||
PrintParameterModifier.WaitTimeBeforeCure,
|
||
|
||
PrintParameterModifier.BottomExposureTime,
|
||
PrintParameterModifier.ExposureTime,
|
||
|
||
PrintParameterModifier.BottomWaitTimeAfterCure,
|
||
PrintParameterModifier.WaitTimeAfterCure,
|
||
|
||
PrintParameterModifier.BottomLiftHeight,
|
||
PrintParameterModifier.BottomLiftSpeed,
|
||
PrintParameterModifier.LiftHeight,
|
||
PrintParameterModifier.LiftSpeed,
|
||
PrintParameterModifier.BottomLiftHeight2,
|
||
PrintParameterModifier.BottomLiftSpeed2,
|
||
PrintParameterModifier.LiftHeight2,
|
||
PrintParameterModifier.LiftSpeed2,
|
||
|
||
PrintParameterModifier.BottomWaitTimeAfterLift,
|
||
PrintParameterModifier.WaitTimeAfterLift,
|
||
|
||
PrintParameterModifier.BottomRetractSpeed,
|
||
PrintParameterModifier.RetractSpeed,
|
||
PrintParameterModifier.BottomRetractHeight2,
|
||
PrintParameterModifier.BottomRetractSpeed2,
|
||
PrintParameterModifier.RetractHeight2,
|
||
PrintParameterModifier.RetractSpeed2,
|
||
|
||
PrintParameterModifier.BottomLightPWM,
|
||
PrintParameterModifier.LightPWM
|
||
};
|
||
|
||
public override PrintParameterModifier[]? PrintParameterPerLayerModifiers { get; } = {
|
||
PrintParameterModifier.PositionZ,
|
||
PrintParameterModifier.LightOffDelay,
|
||
PrintParameterModifier.WaitTimeBeforeCure,
|
||
PrintParameterModifier.ExposureTime,
|
||
PrintParameterModifier.WaitTimeAfterCure,
|
||
PrintParameterModifier.LiftHeight,
|
||
PrintParameterModifier.LiftSpeed,
|
||
PrintParameterModifier.LiftHeight2,
|
||
PrintParameterModifier.LiftSpeed2,
|
||
PrintParameterModifier.WaitTimeAfterLift,
|
||
PrintParameterModifier.RetractSpeed,
|
||
PrintParameterModifier.RetractHeight2,
|
||
PrintParameterModifier.RetractSpeed2,
|
||
PrintParameterModifier.LightPWM
|
||
};
|
||
|
||
public override uint ResolutionX
|
||
{
|
||
get => Header.ResolutionX;
|
||
set => base.ResolutionX = Header.ResolutionX = (ushort)value;
|
||
}
|
||
|
||
public override uint ResolutionY
|
||
{
|
||
get => Header.ResolutionY;
|
||
set => base.ResolutionY = Header.ResolutionY = (ushort)value;
|
||
}
|
||
|
||
public override float LayerHeight
|
||
{
|
||
get => Header.LayerHeight;
|
||
set
|
||
{
|
||
Header.LayerHeight = value;
|
||
RaisePropertyChanged();
|
||
}
|
||
}
|
||
|
||
public override float DisplayWidth
|
||
{
|
||
get => Header.DisplayWidth;
|
||
set => base.DisplayWidth = Header.DisplayWidth = RoundDisplaySize(value);
|
||
}
|
||
|
||
public override float DisplayHeight
|
||
{
|
||
get => Header.DisplayHeight;
|
||
set => base.DisplayHeight = Header.DisplayHeight = RoundDisplaySize(value);
|
||
}
|
||
|
||
public override float MachineZ
|
||
{
|
||
get => Header.MachineZ > 0 ? Header.MachineZ : base.MachineZ;
|
||
set => base.MachineZ = Header.MachineZ = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override FlipDirection DisplayMirror
|
||
{
|
||
get
|
||
{
|
||
if (Header is {MirrorX: true, MirrorY: true}) return FlipDirection.Both;
|
||
if (Header.MirrorX) return FlipDirection.Horizontally;
|
||
if (Header.MirrorY) return FlipDirection.Vertically;
|
||
return FlipDirection.None;
|
||
}
|
||
set
|
||
{
|
||
switch (value)
|
||
{
|
||
case FlipDirection.None:
|
||
Header.MirrorX = false;
|
||
Header.MirrorY = false;
|
||
break;
|
||
case FlipDirection.Horizontally:
|
||
Header.MirrorX = true;
|
||
Header.MirrorY = false;
|
||
break;
|
||
case FlipDirection.Vertically:
|
||
Header.MirrorX = false;
|
||
Header.MirrorY = true;
|
||
break;
|
||
case FlipDirection.Both:
|
||
Header.MirrorX = true;
|
||
Header.MirrorY = true;
|
||
break;
|
||
default:
|
||
throw new ArgumentOutOfRangeException(nameof(value), value, null);
|
||
}
|
||
RaisePropertyChanged();
|
||
}
|
||
}
|
||
|
||
public override byte AntiAliasing
|
||
{
|
||
get => (byte)(Header.AntiAliasingLevel);
|
||
set => base.AntiAliasing = (byte)(Header.AntiAliasingLevel = value);
|
||
}
|
||
|
||
public override uint LayerCount
|
||
{
|
||
get => base.LayerCount;
|
||
set => base.LayerCount = Header.LayerCount = base.LayerCount;
|
||
}
|
||
|
||
public override ushort BottomLayerCount
|
||
{
|
||
get => (ushort) Header.BottomLayerCount;
|
||
set => base.BottomLayerCount = (ushort) (Header.BottomLayerCount = value);
|
||
}
|
||
|
||
public override TransitionLayerTypes TransitionLayerType => TransitionLayerTypes.Software;
|
||
|
||
public override ushort TransitionLayerCount
|
||
{
|
||
get => Header.TransitionLayerCount;
|
||
set => base.TransitionLayerCount = Header.TransitionLayerCount = (ushort) Math.Min(value, MaximumPossibleTransitionLayerCount);
|
||
}
|
||
|
||
public override float BottomLightOffDelay => Header.LightOffDelay;
|
||
|
||
public override float LightOffDelay
|
||
{
|
||
get => Header.LightOffDelay;
|
||
set
|
||
{
|
||
base.LightOffDelay = Header.LightOffDelay = (float)Math.Round(value, 2);
|
||
if (value > 0)
|
||
{
|
||
Header.DelayMode = DelayModes.LightOff;
|
||
}
|
||
}
|
||
}
|
||
|
||
public override float BottomWaitTimeBeforeCure
|
||
{
|
||
get => base.BottomWaitTimeBeforeCure > 0 ? base.BottomWaitTimeBeforeCure : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeBeforeCure ?? 0;
|
||
set
|
||
{
|
||
base.BottomWaitTimeBeforeCure = value;
|
||
Header.DelayMode = DelayModes.WaitTime;
|
||
}
|
||
}
|
||
|
||
|
||
public override float WaitTimeBeforeCure
|
||
{
|
||
get => Header.WaitTimeBeforeCure;
|
||
set
|
||
{
|
||
base.WaitTimeBeforeCure = Header.WaitTimeBeforeCure = Header.WaitTimeBeforeCure = (float)Math.Round(value, 2);
|
||
if (value > 0)
|
||
{
|
||
BottomLightOffDelay = 0;
|
||
LightOffDelay = 0;
|
||
}
|
||
Header.DelayMode = DelayModes.WaitTime;
|
||
}
|
||
}
|
||
|
||
public override float BottomExposureTime
|
||
{
|
||
get => Header.BottomExposureTime;
|
||
set => base.BottomExposureTime = Header.BottomExposureTime = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float BottomWaitTimeAfterCure
|
||
{
|
||
get => base.BottomWaitTimeAfterCure > 0 ? base.BottomWaitTimeAfterCure : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeAfterCure ?? 0;
|
||
set
|
||
{
|
||
base.BottomWaitTimeAfterCure = value;
|
||
Header.DelayMode = DelayModes.WaitTime;
|
||
}
|
||
}
|
||
|
||
public override float WaitTimeAfterCure
|
||
{
|
||
get => Header.WaitTimeAfterCure;
|
||
set
|
||
{
|
||
base.WaitTimeAfterCure = Header.WaitTimeAfterCure = (float)Math.Round(value, 2);
|
||
if (value > 0)
|
||
{
|
||
BottomLightOffDelay = 0;
|
||
LightOffDelay = 0;
|
||
}
|
||
Header.DelayMode = DelayModes.WaitTime;
|
||
}
|
||
}
|
||
|
||
public override float ExposureTime
|
||
{
|
||
get => Header.ExposureTime;
|
||
set => base.ExposureTime = Header.ExposureTime = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float BottomLiftHeight
|
||
{
|
||
get => Header.BottomLiftHeight;
|
||
set => base.BottomLiftHeight = Header.BottomLiftHeight = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float BottomLiftSpeed
|
||
{
|
||
get => Header.BottomLiftSpeed;
|
||
set => base.BottomLiftSpeed = Header.BottomLiftSpeed = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float LiftHeight
|
||
{
|
||
get => Header.LiftHeight;
|
||
set => base.LiftHeight = Header.LiftHeight = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float LiftSpeed
|
||
{
|
||
get => Header.LiftSpeed;
|
||
set => base.LiftSpeed = Header.LiftSpeed = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float BottomLiftHeight2
|
||
{
|
||
get => Header.BottomLiftHeight2;
|
||
set => base.BottomLiftHeight2 = Header.BottomLiftHeight2 = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float BottomLiftSpeed2
|
||
{
|
||
get => Header.BottomLiftSpeed2;
|
||
set => base.BottomLiftSpeed2 = Header.BottomLiftSpeed2 = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float LiftHeight2
|
||
{
|
||
get => Header.LiftHeight2;
|
||
set => base.LiftHeight2 = Header.LiftHeight2 = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float LiftSpeed2
|
||
{
|
||
get => Header.LiftSpeed2;
|
||
set => base.LiftSpeed2 = Header.LiftSpeed2 = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float BottomWaitTimeAfterLift
|
||
{
|
||
get => base.BottomWaitTimeAfterLift > 0 ? base.BottomWaitTimeAfterLift : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeAfterLift ?? 0;
|
||
set
|
||
{
|
||
base.BottomWaitTimeAfterLift = value;
|
||
Header.DelayMode = DelayModes.WaitTime;
|
||
}
|
||
}
|
||
|
||
public override float WaitTimeAfterLift
|
||
{
|
||
get => Header.WaitTimeAfterLift;
|
||
set
|
||
{
|
||
base.WaitTimeAfterLift = Header.WaitTimeAfterLift = Header.WaitTimeAfterLift = (float)Math.Round(value, 2);
|
||
if (value > 0)
|
||
{
|
||
BottomLightOffDelay = 0;
|
||
LightOffDelay = 0;
|
||
}
|
||
Header.DelayMode = DelayModes.WaitTime;
|
||
}
|
||
}
|
||
|
||
public override float BottomRetractSpeed
|
||
{
|
||
get => Header.BottomRetractSpeed;
|
||
set => base.BottomRetractSpeed = Header.BottomRetractSpeed = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float RetractSpeed
|
||
{
|
||
get => Header.RetractSpeed;
|
||
set => base.RetractSpeed = Header.RetractSpeed = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float BottomRetractHeight2
|
||
{
|
||
get => Header.BottomRetractHeight2;
|
||
set
|
||
{
|
||
value = Math.Clamp((float)Math.Round(value, 2), 0, BottomRetractHeightTotal);
|
||
base.BottomRetractHeight2 = Header.BottomRetractHeight2 = value;
|
||
}
|
||
}
|
||
|
||
public override float BottomRetractSpeed2
|
||
{
|
||
get => Header.BottomRetractSpeed2;
|
||
set => base.BottomRetractSpeed2 = Header.BottomRetractSpeed2 = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override float RetractHeight2
|
||
{
|
||
get => Header.RetractHeight2;
|
||
set
|
||
{
|
||
value = Math.Clamp((float)Math.Round(value, 2), 0, RetractHeightTotal);
|
||
base.RetractHeight2 = Header.RetractHeight2 = value;
|
||
}
|
||
}
|
||
|
||
public override float RetractSpeed2
|
||
{
|
||
get => Header.RetractSpeed2;
|
||
set => base.RetractSpeed2 = Header.RetractSpeed2 = (float)Math.Round(value, 2);
|
||
}
|
||
|
||
public override byte BottomLightPWM
|
||
{
|
||
get => (byte)Header.BottomLightPWM;
|
||
set => base.BottomLightPWM = (byte)(Header.BottomLightPWM = value);
|
||
}
|
||
|
||
public override byte LightPWM
|
||
{
|
||
get => (byte)Header.LightPWM;
|
||
set => base.LightPWM = (byte)(Header.LightPWM = value);
|
||
}
|
||
|
||
public override float PrintTime
|
||
{
|
||
get => base.PrintTime;
|
||
set
|
||
{
|
||
base.PrintTime = value;
|
||
Header.PrintTime = (uint)base.PrintTime;
|
||
}
|
||
}
|
||
|
||
public override string MachineName
|
||
{
|
||
get => Header.MachineName;
|
||
set => base.MachineName = Header.MachineName = value;
|
||
}
|
||
|
||
public override float MaterialGrams
|
||
{
|
||
get => Header.MaterialGrams;
|
||
set => base.MaterialGrams = Header.MaterialGrams = (float)Math.Round(value, 3);
|
||
}
|
||
|
||
public override float MaterialCost
|
||
{
|
||
get => (float)Math.Round(Header.MaterialCost, 3);
|
||
set => base.MaterialCost = Header.MaterialCost = (float)Math.Round(value, 3);
|
||
}
|
||
|
||
public override object[] Configs => new object[] { Header, Footer };
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
public GooFile()
|
||
{
|
||
}
|
||
#endregion
|
||
|
||
#region Methods
|
||
|
||
protected override void DecodeInternally(OperationProgress progress)
|
||
{
|
||
using var inputFile = new FileStream(FileFullPath!, FileMode.Open, FileAccess.Read);
|
||
Header = Helpers.Deserialize<FileHeader>(inputFile);
|
||
Debug.WriteLine($"Header: {Header}");
|
||
|
||
var expectedMagic = FileMagic;
|
||
|
||
if (!Header.Magic.SequenceEqual(expectedMagic))
|
||
{
|
||
throw new FileLoadException("Not a valid GOO file! Magic value mismatch", FileFullPath);
|
||
}
|
||
|
||
progress.Reset(OperationProgress.StatusDecodePreviews, ThumbnailsCount);
|
||
|
||
Thumbnails[0] = DecodeImage(DATATYPE_RGB565_BE, Header.SmallPreview565, ThumbnailsOriginalSize![0]);
|
||
progress++;
|
||
Thumbnails[1] = DecodeImage(DATATYPE_RGB565_BE, Header.BigPreview565, ThumbnailsOriginalSize[1]);
|
||
progress++;
|
||
|
||
|
||
progress.Reset(OperationProgress.StatusDecodeLayers, Header.LayerCount);
|
||
Init(Header.LayerCount, DecodeType == FileDecodeType.Partial);
|
||
LayersDefinition = new LayerDef[LayerCount];
|
||
inputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
|
||
|
||
foreach (var batch in BatchLayersIndexes())
|
||
{
|
||
foreach (var layerIndex in batch)
|
||
{
|
||
progress.PauseOrCancelIfRequested();
|
||
|
||
LayersDefinition[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile);
|
||
LayersDefinition[layerIndex].Parent = this;
|
||
if (DecodeType == FileDecodeType.Full)
|
||
{
|
||
LayersDefinition[layerIndex].EncodedRle = inputFile.ReadBytes(LayersDefinition[layerIndex].DataLength);
|
||
}
|
||
else
|
||
{
|
||
inputFile.Seek(LayersDefinition[layerIndex].DataLength, SeekOrigin.Current);
|
||
}
|
||
|
||
inputFile.Seek(2, SeekOrigin.Current); // \n
|
||
Debug.WriteLine($"layer[{layerIndex}]: {LayersDefinition[layerIndex]}");
|
||
}
|
||
|
||
if (DecodeType == FileDecodeType.Full)
|
||
{
|
||
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
|
||
{
|
||
progress.PauseIfRequested();
|
||
var layerDef = LayersDefinition[layerIndex];
|
||
|
||
_layers[layerIndex] = new Layer((uint) layerIndex, layerDef.DecodeImage((uint) layerIndex), this);
|
||
layerDef.CopyTo(_layers[layerIndex]);
|
||
|
||
progress.LockAndIncrement();
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
Footer = Helpers.Deserialize<FileFooter>(inputFile);
|
||
Debug.WriteLine($"Footer: {Footer}");
|
||
if (!Footer.Magic.SequenceEqual(expectedMagic))
|
||
{
|
||
throw new FileLoadException("Not a valid GOO file! Footer magic value mismatch", FileFullPath);
|
||
}
|
||
}
|
||
|
||
protected override void OnBeforeEncode(bool isPartialEncode)
|
||
{
|
||
Header.PerLayerSettings = UsingPerLayerSettings;
|
||
Header.Volume = Volume;
|
||
Header.MaterialGrams = MaterialMilliliters;
|
||
}
|
||
|
||
protected override void EncodeInternally(OperationProgress progress)
|
||
{
|
||
using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Create, FileAccess.Write);
|
||
|
||
progress.Reset(OperationProgress.StatusEncodePreviews, 2);
|
||
|
||
Mat?[] thumbnails = { GetThumbnail(true), GetThumbnail(false) };
|
||
Header.BigPreview565 = EncodeImage(DATATYPE_RGB565_BE, thumbnails[0]!);
|
||
progress++;
|
||
Header.SmallPreview565 = EncodeImage(DATATYPE_RGB565_BE, thumbnails[1]!);
|
||
progress++;
|
||
|
||
Header.LayerDefAddress = (uint)Helpers.Serializer.SizeOf(Header);
|
||
outputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
|
||
|
||
progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
|
||
var layerData = new LayerDef[LayerCount];
|
||
|
||
var delimiter = Delimiter;
|
||
|
||
foreach (var batch in BatchLayersIndexes())
|
||
{
|
||
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
|
||
{
|
||
progress.PauseIfRequested();
|
||
using (var mat = this[layerIndex].LayerMat)
|
||
{
|
||
layerData[layerIndex] = new LayerDef(this, this[layerIndex]);
|
||
layerData[layerIndex].EncodeImage(mat, (uint) layerIndex);
|
||
}
|
||
progress.LockAndIncrement();
|
||
});
|
||
|
||
foreach (var layerIndex in batch)
|
||
{
|
||
progress.PauseOrCancelIfRequested();
|
||
|
||
outputFile.WriteSerialize(layerData[layerIndex]);
|
||
outputFile.WriteBytes(layerData[layerIndex].EncodedRle);
|
||
outputFile.WriteBytes(delimiter);
|
||
|
||
layerData[layerIndex].EncodedRle = null!; // Free
|
||
}
|
||
}
|
||
|
||
// Footer
|
||
outputFile.WriteSerialize(Footer);
|
||
|
||
// Header
|
||
outputFile.Seek(0, SeekOrigin.Begin);
|
||
outputFile.WriteSerialize(Header);
|
||
|
||
Debug.WriteLine("Encode Results:");
|
||
Debug.WriteLine(Header);
|
||
Debug.WriteLine(Footer);
|
||
Debug.WriteLine("-End-");
|
||
}
|
||
|
||
protected override void PartialSaveInternally(OperationProgress progress)
|
||
{
|
||
using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Open, FileAccess.Write);
|
||
outputFile.Seek(0, SeekOrigin.Begin);
|
||
|
||
outputFile.WriteSerialize(Header);
|
||
outputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
|
||
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
|
||
{
|
||
LayersDefinition![layerIndex].SetFrom(this[layerIndex]);
|
||
outputFile.WriteSerialize(LayersDefinition[layerIndex]);
|
||
outputFile.Seek(LayersDefinition[layerIndex].DataLength + 2, SeekOrigin.Current);
|
||
}
|
||
}
|
||
#endregion
|
||
} |