mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
v0.5.2.2
* (Fix) phz: Files with encryption or sliced by chitubox produced black images after save due not setting the image address nor size (Spotted by Burak Cezairli)
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## 21/06/2020 - v0.5.2.2
|
||||
|
||||
* (Fix) phz: Files with encryption or sliced by chitubox produced black images after save due not setting the image address nor size (Spotted by Burak Cezairli)
|
||||
|
||||
## 20/06/2020 - v0.5.2.1
|
||||
|
||||
* (Add) cws: Allow change layer PWM value
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
namespace UVtools.Core
|
||||
{
|
||||
public static class About
|
||||
{
|
||||
public static string Software = "UVtools";
|
||||
public static string Author = "Tiago Conceição";
|
||||
public static string Company = "PTRTECH";
|
||||
public static string Website = "https://github.com/sn4k3/UVtools";
|
||||
public static string Donate = "https://paypal.me/SkillTournament";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
|
||||
namespace UVtools.Core.Extensions
|
||||
{
|
||||
public static class EmguExtensions
|
||||
{
|
||||
public static byte[] GetBytes(this Mat mat)
|
||||
{
|
||||
byte[] data = new byte[mat.Width * mat.Height * mat.NumberOfChannels];
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
|
||||
using (Mat m2 = new Mat(mat.Size, DepthType.Cv8U, 3, handle.AddrOfPinnedObject(), mat.Width * mat.NumberOfChannels)) { }
|
||||
handle.Free();
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace UVtools.Core.Extensions
|
||||
{
|
||||
public static class FileStreamExtensions
|
||||
{
|
||||
public static uint ReadBytes(this FileStream fs, byte[] bytes, int offset = 0)
|
||||
{
|
||||
return (uint)fs.Read(bytes, offset, bytes.Length);
|
||||
}
|
||||
|
||||
public static uint WriteStream(this FileStream fs, MemoryStream stream, int offset = 0)
|
||||
{
|
||||
return fs.WriteBytes(stream.ToArray(), offset);
|
||||
}
|
||||
|
||||
public static uint WriteBytes(this FileStream fs, byte[] bytes, int offset = 0)
|
||||
{
|
||||
fs.Write(bytes, offset, bytes.Length);
|
||||
return (uint)bytes.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace UVtools.Core.Extensions
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts stream into byte array
|
||||
/// </summary>
|
||||
/// <param name="stream">Input</param>
|
||||
/// <returns>Byte array data</returns>
|
||||
public static byte[] ToArray(this Stream stream)
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace UVtools.Core.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Upper the first character in a string
|
||||
/// </summary>
|
||||
/// <param name="input">Input string</param>
|
||||
/// <returns>Modified string with fist character upper</returns>
|
||||
public static string FirstCharToUpper(this string input)
|
||||
{
|
||||
switch (input)
|
||||
{
|
||||
case null: throw new ArgumentNullException(nameof(input));
|
||||
case "": throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input));
|
||||
default: return input.First().ToString().ToUpper() + input.Substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a string into a target type
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Target type to convert into</typeparam>
|
||||
/// <param name="input">Value</param>
|
||||
/// <returns>Converted value into target type</returns>
|
||||
public static T Convert<T>(this string input)
|
||||
{
|
||||
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
if (converter != null)
|
||||
{
|
||||
//Cast ConvertFromString(string text) : object to (T)
|
||||
return (T)converter.ConvertFromString(input);
|
||||
}
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
||||
namespace UVtools.Core.Extensions
|
||||
{
|
||||
public static class ZipArchiveExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to specify what our overwrite policy
|
||||
/// is for files we are extracting.
|
||||
/// </summary>
|
||||
public enum Overwrite
|
||||
{
|
||||
Always,
|
||||
IfNewer,
|
||||
Never
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to identify what we will do if we are
|
||||
/// trying to create a zip file and it already
|
||||
/// exists.
|
||||
/// </summary>
|
||||
public enum ArchiveAction
|
||||
{
|
||||
Merge,
|
||||
Replace,
|
||||
Error,
|
||||
Ignore
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unzips the specified file to the given folder in a safe
|
||||
/// manner. This plans for missing paths and existing files
|
||||
/// and handles them gracefully.
|
||||
/// </summary>
|
||||
/// <param name="sourceArchiveFileName">
|
||||
/// The name of the zip file to be extracted
|
||||
/// </param>
|
||||
/// <param name="destinationDirectoryName">
|
||||
/// The directory to extract the zip file to
|
||||
/// </param>
|
||||
/// <param name="overwriteMethod">
|
||||
/// Specifies how we are going to handle an existing file.
|
||||
/// The default is IfNewer.
|
||||
/// </param>
|
||||
public static void ImprovedExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Overwrite overwriteMethod = Overwrite.IfNewer)
|
||||
{
|
||||
//Opens the zip file up to be read
|
||||
using (ZipArchive archive = ZipFile.OpenRead(sourceArchiveFileName))
|
||||
{
|
||||
archive.ImprovedExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, overwriteMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unzips the specified file to the given folder in a safe
|
||||
/// manner. This plans for missing paths and existing files
|
||||
/// and handles them gracefully.
|
||||
/// </summary>
|
||||
/// <param name="sourceArchiveFileName">
|
||||
/// The name of the zip file to be extracted
|
||||
/// </param>
|
||||
/// <param name="destinationDirectoryName">
|
||||
/// The directory to extract the zip file to
|
||||
/// </param>
|
||||
/// <param name="overwriteMethod">
|
||||
/// Specifies how we are going to handle an existing file.
|
||||
/// The default is IfNewer.
|
||||
/// </param>
|
||||
public static void ImprovedExtractToDirectory(this ZipArchive archive, string sourceArchiveFileName, string destinationDirectoryName, Overwrite overwriteMethod = Overwrite.IfNewer)
|
||||
{
|
||||
//Loops through each file in the zip file
|
||||
foreach (ZipArchiveEntry file in archive.Entries)
|
||||
{
|
||||
file.ImprovedExtractToFile(destinationDirectoryName, overwriteMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely extracts a single file from a zip file
|
||||
/// </summary>
|
||||
/// <param name="file">
|
||||
/// The zip entry we are pulling the file from
|
||||
/// </param>
|
||||
/// <param name="destinationPath">
|
||||
/// The root of where the file is going
|
||||
/// </param>
|
||||
/// <param name="overwriteMethod">
|
||||
/// Specifies how we are going to handle an existing file.
|
||||
/// The default is Overwrite.IfNewer.
|
||||
/// </param>
|
||||
public static void ImprovedExtractToFile(this ZipArchiveEntry file, string destinationPath, Overwrite overwriteMethod = Overwrite.IfNewer)
|
||||
{
|
||||
//Gets the complete path for the destination file, including any
|
||||
//relative paths that were in the zip file
|
||||
string destinationFileName = Path.Combine(destinationPath, file.FullName);
|
||||
|
||||
//Gets just the new path, minus the file name so we can create the
|
||||
//directory if it does not exist
|
||||
string destinationFilePath = Path.GetDirectoryName(destinationFileName);
|
||||
|
||||
//Creates the directory (if it doesn't exist) for the new path
|
||||
Directory.CreateDirectory(destinationFilePath);
|
||||
|
||||
//Determines what to do with the file based upon the
|
||||
//method of overwriting chosen
|
||||
switch (overwriteMethod)
|
||||
{
|
||||
case Overwrite.Always:
|
||||
//Just put the file in and overwrite anything that is found
|
||||
file.ExtractToFile(destinationFileName, true);
|
||||
break;
|
||||
case Overwrite.IfNewer:
|
||||
//Checks to see if the file exists, and if so, if it should
|
||||
//be overwritten
|
||||
if (!File.Exists(destinationFileName) || File.GetLastWriteTime(destinationFileName) < file.LastWriteTime)
|
||||
{
|
||||
//Either the file didn't exist or this file is newer, so
|
||||
//we will extract it and overwrite any existing file
|
||||
file.ExtractToFile(destinationFileName, true);
|
||||
}
|
||||
break;
|
||||
case Overwrite.Never:
|
||||
//Put the file in if it is new but ignores the
|
||||
//file if it already exists
|
||||
if (!File.Exists(destinationFileName))
|
||||
{
|
||||
file.ExtractToFile(destinationFileName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to add files to an archive, whether the archive
|
||||
/// already exists or not
|
||||
/// </summary>
|
||||
/// <param name="archiveFullName">
|
||||
/// The name of the archive to you want to add your files to
|
||||
/// </param>
|
||||
/// <param name="files">
|
||||
/// A set of file names that are to be added
|
||||
/// </param>
|
||||
/// <param name="action">
|
||||
/// Specifies how we are going to handle an existing archive
|
||||
/// </param>
|
||||
/// <param name="compression">
|
||||
/// Specifies what type of compression to use - defaults to Optimal
|
||||
/// </param>
|
||||
public static void AddToArchive(string archiveFullName,
|
||||
List<string> files,
|
||||
ArchiveAction action = ArchiveAction.Replace,
|
||||
Overwrite fileOverwrite = Overwrite.IfNewer,
|
||||
CompressionLevel compression = CompressionLevel.Optimal)
|
||||
{
|
||||
//Identifies the mode we will be using - the default is Create
|
||||
ZipArchiveMode mode = ZipArchiveMode.Create;
|
||||
|
||||
//Determines if the zip file even exists
|
||||
bool archiveExists = File.Exists(archiveFullName);
|
||||
|
||||
//Figures out what to do based upon our specified overwrite method
|
||||
switch (action)
|
||||
{
|
||||
case ArchiveAction.Merge:
|
||||
//Sets the mode to update if the file exists, otherwise
|
||||
//the default of Create is fine
|
||||
if (archiveExists)
|
||||
{
|
||||
mode = ZipArchiveMode.Update;
|
||||
}
|
||||
break;
|
||||
case ArchiveAction.Replace:
|
||||
//Deletes the file if it exists. Either way, the default
|
||||
//mode of Create is fine
|
||||
if (archiveExists)
|
||||
{
|
||||
File.Delete(archiveFullName);
|
||||
}
|
||||
break;
|
||||
case ArchiveAction.Error:
|
||||
//Throws an error if the file exists
|
||||
if (archiveExists)
|
||||
{
|
||||
throw new IOException(String.Format("The zip file {0} already exists.", archiveFullName));
|
||||
}
|
||||
break;
|
||||
case ArchiveAction.Ignore:
|
||||
//Closes the method silently and does nothing
|
||||
if (archiveExists)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//Opens the zip file in the mode we specified
|
||||
using (ZipArchive zipFile = ZipFile.Open(archiveFullName, mode))
|
||||
{
|
||||
//This is a bit of a hack and should be refactored - I am
|
||||
//doing a similar foreach loop for both modes, but for Create
|
||||
//I am doing very little work while Update gets a lot of
|
||||
//code. This also does not handle any other mode (of
|
||||
//which there currently wouldn't be one since we don't
|
||||
//use Read here).
|
||||
if (mode == ZipArchiveMode.Create)
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
//Adds the file to the archive
|
||||
zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
var fileInZip = (from f in zipFile.Entries
|
||||
where f.Name == Path.GetFileName(file)
|
||||
select f).FirstOrDefault();
|
||||
|
||||
switch (fileOverwrite)
|
||||
{
|
||||
case Overwrite.Always:
|
||||
//Deletes the file if it is found
|
||||
if (fileInZip != null)
|
||||
{
|
||||
fileInZip.Delete();
|
||||
}
|
||||
|
||||
//Adds the file to the archive
|
||||
zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression);
|
||||
|
||||
break;
|
||||
case Overwrite.IfNewer:
|
||||
//This is a bit trickier - we only delete the file if it is
|
||||
//newer, but if it is newer or if the file isn't already in
|
||||
//the zip file, we will write it to the zip file
|
||||
if (fileInZip != null)
|
||||
{
|
||||
//Deletes the file only if it is older than our file.
|
||||
//Note that the file will be ignored if the existing file
|
||||
//in the archive is newer.
|
||||
if (fileInZip.LastWriteTime < File.GetLastWriteTime(file))
|
||||
{
|
||||
fileInZip.Delete();
|
||||
|
||||
//Adds the file to the archive
|
||||
zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//The file wasn't already in the zip file so add it to the archive
|
||||
zipFile.CreateEntryFromFile(file, Path.GetFileName(file), compression);
|
||||
}
|
||||
break;
|
||||
case Overwrite.Never:
|
||||
//Don't do anything - this is a decision that you need to
|
||||
//consider, however, since this will mean that no file will
|
||||
//be written. You could write a second copy to the zip with
|
||||
//the same name (not sure that is wise, however).
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or put a file into archive
|
||||
/// </summary>
|
||||
/// <param name="input"><see cref="ZipArchive"/></param>
|
||||
/// <param name="filename">Filename to create</param>
|
||||
/// <returns>Created <see cref="ZipArchiveEntry"/></returns>
|
||||
public static ZipArchiveEntry GetPutFile(this ZipArchive input, string filename)
|
||||
{
|
||||
return input.GetEntry(filename) ?? input.CreateEntry(filename);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create or update a file into archive and write content to it
|
||||
/// </summary>
|
||||
/// <param name="input"><see cref="ZipArchive"/></param>
|
||||
/// <param name="filename">Filename to create</param>
|
||||
/// <param name="content">Content to write</param>
|
||||
/// <returns>Created <see cref="ZipArchiveEntry"/></returns>
|
||||
public static ZipArchiveEntry PutFileContent(this ZipArchive input, string filename, string content)
|
||||
{
|
||||
ZipArchiveEntry entry = input.GetEntry(filename) ?? input.CreateEntry(filename);
|
||||
|
||||
if (string.IsNullOrEmpty(content)) return entry;
|
||||
Stream stream = entry.Open();
|
||||
stream.SetLength(0);
|
||||
using (TextWriter tw = new StreamWriter(stream))
|
||||
{
|
||||
tw.Write(content);
|
||||
tw.Close();
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create or update a file into archive and write content to it
|
||||
/// </summary>
|
||||
/// <param name="input"><see cref="ZipArchive"/></param>
|
||||
/// <param name="filename">Filename to create</param>
|
||||
/// <param name="content">Content to write</param>
|
||||
/// <returns>Created <see cref="ZipArchiveEntry"/></returns>
|
||||
public static ZipArchiveEntry PutFileContent(this ZipArchive input, string filename, byte[] content)
|
||||
{
|
||||
ZipArchiveEntry entry = input.GetEntry(filename) ?? input.CreateEntry(filename);
|
||||
|
||||
if (ReferenceEquals(content, null)) return entry;
|
||||
Stream stream = entry.Open();
|
||||
stream.SetLength(0);
|
||||
stream.Write(content, 0, content.Length);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UVtools.Parser
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a file extension for slicer file formats
|
||||
/// </summary>
|
||||
public sealed class FileExtension
|
||||
{
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// Gets the extension name without the dot (.)
|
||||
/// </summary>
|
||||
public string Extension { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the extension description
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file filter for open and save dialogs
|
||||
/// </summary>
|
||||
public string Filter => $@"{Description} (*.{Extension})|*.{Extension}";
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="extension">The extension name without the dot (.)</param>
|
||||
/// <param name="description">The extension description</param>
|
||||
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<FileExtension>
|
||||
{
|
||||
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<FileExtension> ExtensionComparer { get; } = new ExtensionEqualityComparer();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,697 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Emgu.CV;
|
||||
using UVtools.Core.Extensions;
|
||||
using UVtools.Parser;
|
||||
|
||||
namespace UVtools.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Slicer <see cref="FileFormat"/> representation
|
||||
/// </summary>
|
||||
public abstract class FileFormat : IFileFormat, IDisposable, IEquatable<FileFormat>, IEnumerable<Layer>
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of file format types
|
||||
/// </summary>
|
||||
public enum FileFormatType : byte
|
||||
{
|
||||
Archive,
|
||||
Binary
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of file thumbnail size types
|
||||
/// </summary>
|
||||
public enum FileThumbnailSize : byte
|
||||
{
|
||||
Small = 0,
|
||||
Large
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Sub Classes
|
||||
/// <summary>
|
||||
/// Available Print Parameters to modify
|
||||
/// </summary>
|
||||
public class PrintParameterModifier
|
||||
{
|
||||
|
||||
#region Instances
|
||||
public static PrintParameterModifier InitialLayerCount { get; } = new PrintParameterModifier("Initial Layer Count", @"Modify 'Initial Layer Count' value", null,0, ushort.MaxValue);
|
||||
public static PrintParameterModifier InitialExposureSeconds { get; } = new PrintParameterModifier("Initial Exposure Time", @"Modify 'Initial Exposure Time' seconds", "s", 0.1M, byte.MaxValue);
|
||||
public static PrintParameterModifier ExposureSeconds { get; } = new PrintParameterModifier("Exposure Time", @"Modify 'Exposure Time' seconds", "s", 0.1M, byte.MaxValue);
|
||||
|
||||
public static PrintParameterModifier BottomLayerOffTime { get; } = new PrintParameterModifier("Bottom Layer Off Time", @"Modify 'Bottom Layer Off Time' seconds", "s");
|
||||
public static PrintParameterModifier LayerOffTime { get; } = new PrintParameterModifier("Layer Off Time", @"Modify 'Layer Off Time' seconds", "s");
|
||||
public static PrintParameterModifier BottomLiftHeight { get; } = new PrintParameterModifier("Bottom Lift Height", @"Modify 'Bottom Lift Height' millimeters between bottom layers", "mm");
|
||||
public static PrintParameterModifier BottomLiftSpeed { get; } = new PrintParameterModifier("Bottom Lift Speed", @"Modify 'Bottom Lift Speed' mm/min between bottom layers", "mm/min");
|
||||
public static PrintParameterModifier LiftHeight { get; } = new PrintParameterModifier("Lift Height", @"Modify 'Lift Height' millimeters between layers", "mm");
|
||||
public static PrintParameterModifier LiftSpeed { get; } = new PrintParameterModifier("Lift Speed", @"Modify 'Lift Speed' mm/min between layers", "mm/min", 10, 5000);
|
||||
public static PrintParameterModifier RetractSpeed { get; } = new PrintParameterModifier("Retract Speed", @"Modify 'Retract Speed' mm/min between layers", "mm/min", 10, 5000);
|
||||
|
||||
public static PrintParameterModifier BottomLightPWM { get; } = new PrintParameterModifier("Bottom Light PWM", @"Modify 'Bottom Light PWM' value", null, 50, byte.MaxValue);
|
||||
public static PrintParameterModifier LightPWM { get; } = new PrintParameterModifier("Light PWM", @"Modify 'Light PWM' value", null, 50, byte.MaxValue);
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value unit
|
||||
/// </summary>
|
||||
public string ValueUnit { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum value
|
||||
/// </summary>
|
||||
public decimal Minimum { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum value
|
||||
/// </summary>
|
||||
public decimal Maximum { get; }
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public PrintParameterModifier(string name, string description, string valueUnit = null, decimal minimum = 0, decimal maximum = 1000)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
ValueUnit = valueUnit ?? string.Empty;
|
||||
Minimum = minimum;
|
||||
Maximum = maximum;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(Name)}: {Name}, {nameof(Description)}: {Description}, {nameof(ValueUnit)}: {ValueUnit}, {nameof(Minimum)}: {Minimum}, {nameof(Maximum)}: {Maximum}";
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
private const string ExtractConfigFileName = "Configuration";
|
||||
private const string ExtractConfigFileExtension = "ini";
|
||||
#endregion
|
||||
|
||||
#region Static Methods
|
||||
/// <summary>
|
||||
/// Gets the available formats to process
|
||||
/// </summary>
|
||||
public static FileFormat[] AvaliableFormats { get; } =
|
||||
{
|
||||
new SL1File(), // Prusa SL1
|
||||
new ChituboxZipFile(), // Zip
|
||||
new ChituboxFile(), // cbddlp, cbt, photon
|
||||
new PHZFile(), // phz
|
||||
new PWSFile(), // PSW
|
||||
new ZCodexFile(), // zcodex
|
||||
new CWSFile(), // CWS
|
||||
new ImageFile(), // images
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets all filters for open and save file dialogs
|
||||
/// </summary>
|
||||
public static string AllFileFilters =>
|
||||
AvaliableFormats.Aggregate(string.Empty,
|
||||
(current, fileFormat) => string.IsNullOrEmpty(current)
|
||||
? fileFormat.FileFilter
|
||||
: $"{current}|" + fileFormat.FileFilter)
|
||||
+
|
||||
AvaliableFormats.Aggregate("|All slicer files|",
|
||||
(current, fileFormat) => current.EndsWith("|")
|
||||
? $"{current}{fileFormat.FileFilterExtensionsOnly}"
|
||||
: $"{current};{fileFormat.FileFilterExtensionsOnly}");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of available file extensions
|
||||
/// </summary>
|
||||
public static byte FileExtensionsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return AvaliableFormats.Aggregate<FileFormat, byte>(0, (current, fileFormat) => (byte) (current + fileFormat.FileExtensions.Length));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find <see cref="FileFormat"/> by an extension
|
||||
/// </summary>
|
||||
/// <param name="extension">Extension name to find</param>
|
||||
/// <param name="isFilePath">True if <see cref="extension"/> is a file path rather than only a extension name</param>
|
||||
/// <param name="createNewInstance">True to create a new instance of found file format, otherwise will return a pre created one which should be used for read-only purpose</param>
|
||||
/// <returns><see cref="FileFormat"/> object or null if not found</returns>
|
||||
public static FileFormat FindByExtension(string extension, bool isFilePath = false, bool createNewInstance = false)
|
||||
{
|
||||
return (from fileFormat in AvaliableFormats where fileFormat.IsExtensionValid(extension, isFilePath) select createNewInstance ? (FileFormat) Activator.CreateInstance(fileFormat.GetType()) : fileFormat).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find <see cref="FileFormat"/> by an type
|
||||
/// </summary>
|
||||
/// <param name="type">Type to find</param>
|
||||
/// <param name="createNewInstance">True to create a new instance of found file format, otherwise will return a pre created one which should be used for read-only purpose</param>
|
||||
/// <returns><see cref="FileFormat"/> object or null if not found</returns>
|
||||
public static FileFormat FindByType(Type type, bool createNewInstance = false)
|
||||
{
|
||||
return (from t in AvaliableFormats where type == t.GetType() select createNewInstance ? (FileFormat) Activator.CreateInstance(type) : t).FirstOrDefault();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public abstract FileFormatType FileType { get; }
|
||||
|
||||
public abstract FileExtension[] FileExtensions { get; }
|
||||
public abstract Type[] ConvertToFormats { get; }
|
||||
|
||||
public abstract PrintParameterModifier[] PrintParameterModifiers { get; }
|
||||
|
||||
public string FileFilter {
|
||||
get
|
||||
{
|
||||
var result = string.Empty;
|
||||
|
||||
foreach (var fileExt in FileExtensions)
|
||||
{
|
||||
if (!ReferenceEquals(result, string.Empty))
|
||||
{
|
||||
result += '|';
|
||||
}
|
||||
result += fileExt.Filter;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileFilterExtensionsOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = string.Empty;
|
||||
|
||||
foreach (var fileExt in FileExtensions)
|
||||
{
|
||||
if (!ReferenceEquals(result, string.Empty))
|
||||
{
|
||||
result += ';';
|
||||
}
|
||||
result += $"*.{fileExt.Extension}";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileFullPath { get; set; }
|
||||
|
||||
public abstract byte ThumbnailsCount { get; }
|
||||
|
||||
public byte CreatedThumbnailsCount {
|
||||
get
|
||||
{
|
||||
if (ReferenceEquals(Thumbnails, null)) return 0;
|
||||
byte count = 0;
|
||||
|
||||
foreach (var thumbnail in Thumbnails)
|
||||
{
|
||||
if (ReferenceEquals(thumbnail, null)) continue;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Size[] ThumbnailsOriginalSize { get; }
|
||||
|
||||
public Mat[] Thumbnails { get; set; }
|
||||
public LayerManager LayerManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if any layer got modified
|
||||
/// </summary>
|
||||
public bool ModifiedLayers => LayerManager.IsModified;
|
||||
|
||||
public abstract uint ResolutionX { get; }
|
||||
|
||||
public abstract uint ResolutionY { get; }
|
||||
public bool HaveAntiAliasing => AntiAliasing > 1;
|
||||
public abstract byte AntiAliasing { get; }
|
||||
|
||||
public abstract float LayerHeight { get; }
|
||||
|
||||
public float TotalHeight => (float)Math.Round(LayerCount * LayerHeight, 2);
|
||||
|
||||
public uint LayerCount => LayerManager.Count;
|
||||
|
||||
public abstract ushort InitialLayerCount { get; }
|
||||
|
||||
public abstract float InitialExposureTime { get; }
|
||||
|
||||
public abstract float LayerExposureTime { get; }
|
||||
|
||||
public abstract float LiftHeight { get; }
|
||||
|
||||
public abstract float RetractSpeed { get; }
|
||||
|
||||
public abstract float LiftSpeed { get; }
|
||||
|
||||
public abstract float PrintTime { get; }
|
||||
|
||||
public abstract float UsedMaterial { get; }
|
||||
|
||||
public abstract float MaterialCost { get; }
|
||||
|
||||
public abstract string MaterialName { get; }
|
||||
|
||||
public abstract string MachineName { get; }
|
||||
|
||||
public StringBuilder GCode { get; set; }
|
||||
|
||||
public abstract object[] Configs { get; }
|
||||
|
||||
public bool IsValid => !ReferenceEquals(FileFullPath, null);
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
protected FileFormat()
|
||||
{
|
||||
Thumbnails = new Mat[ThumbnailsCount];
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Indexers
|
||||
public Layer this[int index]
|
||||
{
|
||||
get => LayerManager[index];
|
||||
set => LayerManager[index] = value;
|
||||
}
|
||||
|
||||
public Layer this[uint index]
|
||||
{
|
||||
get => LayerManager[index];
|
||||
set => LayerManager[index] = value;
|
||||
}
|
||||
|
||||
public Layer this[long index]
|
||||
{
|
||||
get => LayerManager[index];
|
||||
set => LayerManager[index] = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Numerators
|
||||
public IEnumerator<Layer> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<Layer>)LayerManager.Layers).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as FileFormat);
|
||||
}
|
||||
|
||||
public bool Equals(FileFormat other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return FileFullPath.Equals(other.FileFullPath);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (FileFullPath != null ? FileFullPath.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
public virtual void Clear()
|
||||
{
|
||||
FileFullPath = null;
|
||||
LayerManager = null;
|
||||
GCode = null;
|
||||
|
||||
if (!ReferenceEquals(Thumbnails, null))
|
||||
{
|
||||
for (int i = 0; i < ThumbnailsCount; i++)
|
||||
{
|
||||
Thumbnails[i]?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
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 (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 FileExtensions.Any(fileExtension => fileExtension.Extension.Equals(extension, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
public string GetFileExtensions(string prepend = ".", string separator = ", ")
|
||||
{
|
||||
var result = string.Empty;
|
||||
|
||||
foreach (var fileExt in FileExtensions)
|
||||
{
|
||||
if (!ReferenceEquals(result, string.Empty))
|
||||
{
|
||||
result += separator;
|
||||
}
|
||||
result += $"{prepend}{fileExt.Extension}";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Mat GetThumbnail(uint maxHeight = 400)
|
||||
{
|
||||
for (int i = 0; i < ThumbnailsCount; i++)
|
||||
{
|
||||
if(ReferenceEquals(Thumbnails[i], null)) continue;
|
||||
if (Thumbnails[i].Height <= maxHeight) return Thumbnails[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetThumbnails(Mat[] images)
|
||||
{
|
||||
for (var i = 0; i < ThumbnailsCount; i++)
|
||||
{
|
||||
Thumbnails[i] = images[Math.Min(i, images.Length - 1)].Clone();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetThumbnails(Mat image)
|
||||
{
|
||||
for (var i = 0; i < ThumbnailsCount; i++)
|
||||
{
|
||||
Thumbnails[i] = image.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Encode(string fileFullPath)
|
||||
{
|
||||
FileFullPath = fileFullPath;
|
||||
|
||||
if (File.Exists(fileFullPath))
|
||||
{
|
||||
File.Delete(fileFullPath);
|
||||
}
|
||||
|
||||
for (var i = 0; i < Thumbnails.Length; i++)
|
||||
{
|
||||
if (ReferenceEquals(Thumbnails[i], null)) continue;
|
||||
Mat output = new Mat();
|
||||
CvInvoke.Resize(Thumbnails[i], output, new Size(ThumbnailsOriginalSize[i].Width, ThumbnailsOriginalSize[i].Height));
|
||||
Thumbnails[i] = output;
|
||||
}
|
||||
}
|
||||
|
||||
/*public virtual void BeginEncode(string fileFullPath)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public abstract void InsertLayerImageEncode(Image<L8> image, uint layerIndex);
|
||||
|
||||
public abstract void EndEncode();*/
|
||||
|
||||
public virtual void Decode(string fileFullPath)
|
||||
{
|
||||
Clear();
|
||||
FileValidation(fileFullPath);
|
||||
FileFullPath = fileFullPath;
|
||||
}
|
||||
|
||||
public virtual void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true)
|
||||
{
|
||||
/*if (emptyFirst)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(path);
|
||||
|
||||
foreach (FileInfo file in di.GetFiles())
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
foreach (DirectoryInfo dir in di.GetDirectories())
|
||||
{
|
||||
dir.Delete(true);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
//if (!Directory.Exists(path))
|
||||
//{
|
||||
Directory.CreateDirectory(path);
|
||||
//}
|
||||
|
||||
|
||||
if (FileType == FileFormatType.Archive)
|
||||
{
|
||||
//ZipFile.ExtractToDirectory(FileFullPath, path);
|
||||
ZipArchiveExtensions.ImprovedExtractToDirectory(FileFullPath, path, ZipArchiveExtensions.Overwrite.Always);
|
||||
return;
|
||||
}
|
||||
|
||||
if (genericConfigExtract)
|
||||
{
|
||||
if (!ReferenceEquals(Configs, null))
|
||||
{
|
||||
using (TextWriter tw = new StreamWriter(Path.Combine(path, $"{ExtractConfigFileName}.{ExtractConfigFileExtension}"), false))
|
||||
{
|
||||
foreach (var config in Configs)
|
||||
{
|
||||
var type = config.GetType();
|
||||
tw.WriteLine($"[{type.Name}]");
|
||||
foreach (var property in type.GetProperties())
|
||||
{
|
||||
tw.WriteLine($"{property.Name} = {property.GetValue(config)}");
|
||||
}
|
||||
|
||||
tw.WriteLine();
|
||||
}
|
||||
|
||||
tw.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (genericLayersExtract)
|
||||
{
|
||||
uint i = 0;
|
||||
if (!ReferenceEquals(Thumbnails, null))
|
||||
{
|
||||
foreach (var thumbnail in Thumbnails)
|
||||
{
|
||||
if (ReferenceEquals(thumbnail, null))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
thumbnail.Save(Path.Combine(path, $"Thumbnail{i}.png"));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (LayerCount > 0)
|
||||
{
|
||||
Parallel.ForEach(this, (layer) =>
|
||||
{
|
||||
var byteArr = layer.CompressedBytes;
|
||||
using (FileStream stream = File.Create(Path.Combine(path, $"Layer{layer.Index}.png"),
|
||||
byteArr.Length))
|
||||
{
|
||||
stream.Write(byteArr, 0, byteArr.Length);
|
||||
stream.Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Parallel.For(0, LayerCount, layerIndex => {
|
||||
var byteArr = this[layerIndex].RawData;
|
||||
using (FileStream stream = File.Create(Path.Combine(path, $"Layer{layerIndex}.png"), byteArr.Length))
|
||||
{
|
||||
stream.Write(byteArr, 0, byteArr.Length);
|
||||
stream.Close();
|
||||
}
|
||||
});*/
|
||||
/*for (i = 0; i < LayerCount; i++)
|
||||
{
|
||||
var byteArr = GetLayer(i);
|
||||
using (FileStream stream = File.Create(Path.Combine(path, $"Layer{i}.png"), byteArr.Length))
|
||||
{
|
||||
stream.Write(byteArr, 0, byteArr.Length);
|
||||
stream.Close();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
public virtual float GetHeightFromLayer(uint layerIndex, bool realHeight = true)
|
||||
{
|
||||
return (float)Math.Round((layerIndex+(realHeight ? 1 : 0)) * LayerHeight, 2);
|
||||
}
|
||||
|
||||
public T GetInitialLayerValueOrNormal<T>(uint layerIndex, T initialLayerValue, T normalLayerValue)
|
||||
{
|
||||
return layerIndex < InitialLayerCount ? initialLayerValue : normalLayerValue;
|
||||
}
|
||||
|
||||
public virtual object GetValueFromPrintParameterModifier(PrintParameterModifier modifier)
|
||||
{
|
||||
if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount))
|
||||
return InitialLayerCount;
|
||||
if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds))
|
||||
return InitialExposureTime;
|
||||
if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds))
|
||||
return LayerExposureTime;
|
||||
|
||||
if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight))
|
||||
return LiftHeight;
|
||||
if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed))
|
||||
return LiftSpeed;
|
||||
if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed))
|
||||
return RetractSpeed;
|
||||
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, object value)
|
||||
{
|
||||
return SetValueFromPrintParameterModifier(modifier, value.ToString());
|
||||
}
|
||||
|
||||
public abstract bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value);
|
||||
|
||||
public void Save()
|
||||
{
|
||||
SaveAs();
|
||||
}
|
||||
|
||||
public abstract void SaveAs(string filePath = null);
|
||||
|
||||
public abstract bool Convert(Type to, string fileFullPath);
|
||||
public bool Convert(FileFormat to, string fileFullPath)
|
||||
{
|
||||
return Convert(to.GetType(), fileFullPath);
|
||||
}
|
||||
|
||||
public void Resize(uint startLayerIndex, uint endLayerIndex, float x, float y, bool fade)
|
||||
{
|
||||
if (x == 1f && y == 1f) return;
|
||||
|
||||
Parallel.For(startLayerIndex, endLayerIndex+1, /*new ParallelOptions { MaxDegreeOfParallelism = 1 },*/ layerIndex =>
|
||||
{
|
||||
var newX = x;
|
||||
var newY = y;
|
||||
if (fade)
|
||||
{
|
||||
if (newX != 1f)
|
||||
{
|
||||
double steps = Math.Abs(newX - 1.0) / (endLayerIndex - startLayerIndex);
|
||||
//maxIteration = Math.Max(iterationsStart, iterationsEnd);
|
||||
|
||||
newX = (float) (newX < 1f
|
||||
? newX + (layerIndex - startLayerIndex) * steps
|
||||
: newX - (layerIndex - startLayerIndex) * steps);
|
||||
|
||||
// constrain
|
||||
//iterations = Math.Min(Math.Max(1, iterations), maxIteration);
|
||||
}
|
||||
|
||||
if (y != 1f)
|
||||
{
|
||||
double steps = Math.Abs(newY - 1.0) / (endLayerIndex - startLayerIndex);
|
||||
//maxIteration = Math.Max(iterationsStart, iterationsEnd);
|
||||
|
||||
newY = (float) (newY < 1f
|
||||
? newY + (layerIndex - startLayerIndex) * steps
|
||||
: newY - (layerIndex - startLayerIndex) * steps);
|
||||
|
||||
// constrain
|
||||
//iterations = Math.Min(Math.Max(1, iterations), maxIteration);
|
||||
}
|
||||
}
|
||||
|
||||
if (newX == 1f && newY == 1f) return;
|
||||
|
||||
|
||||
|
||||
this[layerIndex].Resize(newX, newY);
|
||||
});
|
||||
}
|
||||
|
||||
public byte ValidateAntiAliasingLevel()
|
||||
{
|
||||
if (AntiAliasing < 2) return 1;
|
||||
if(AntiAliasing % 2 != 0) throw new ArgumentException("AntiAliasing must be multiples of 2, otherwise use 0 or 1 to disable it", nameof(AntiAliasing));
|
||||
return AntiAliasing;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using BinarySerialization;
|
||||
using Newtonsoft.Json;
|
||||
using UVtools.Core.Extensions;
|
||||
|
||||
namespace UVtools.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class with utilities
|
||||
/// </summary>
|
||||
public static class Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BinarySerializer"/> instance
|
||||
/// </summary>
|
||||
public static BinarySerializer Serializer { get; } = new BinarySerializer {Endianness = Endianness.Little };
|
||||
|
||||
public static MemoryStream Serialize(object value)
|
||||
{
|
||||
MemoryStream stream = new MemoryStream();
|
||||
Serializer.Serialize(stream, value);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(Stream stream)
|
||||
{
|
||||
return Serializer.Deserialize<T>(stream);
|
||||
}
|
||||
|
||||
public static uint SerializeWriteFileStream(FileStream fs, object value, int offset = 0)
|
||||
{
|
||||
using (MemoryStream stream = Helpers.Serialize(value))
|
||||
{
|
||||
return fs.WriteStream(stream, offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static T JsonDeserializeObject<T>(Stream stream)
|
||||
{
|
||||
using (TextReader tr = new StreamReader(stream))
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(tr.ReadToEnd());
|
||||
}
|
||||
}
|
||||
|
||||
public static SHA1CryptoServiceProvider SHA1 { get; } = new SHA1CryptoServiceProvider();
|
||||
public static string ComputeSHA1Hash(byte[] input)
|
||||
{
|
||||
return Convert.ToBase64String(SHA1.ComputeHash(input));
|
||||
}
|
||||
|
||||
public static bool SetPropertyValue(PropertyInfo attribute, object obj, string value)
|
||||
{
|
||||
var name = attribute.PropertyType.Name.ToLower();
|
||||
switch (name)
|
||||
{
|
||||
case "string":
|
||||
attribute.SetValue(obj, value.Convert<string>());
|
||||
return true;
|
||||
case "boolean":
|
||||
if(char.IsDigit(value[0]))
|
||||
attribute.SetValue(obj, !value.Equals(0));
|
||||
else
|
||||
attribute.SetValue(obj, value.Equals("True", StringComparison.InvariantCultureIgnoreCase));
|
||||
return true;
|
||||
case "byte":
|
||||
attribute.SetValue(obj, value.Convert<byte>());
|
||||
return true;
|
||||
case "uint16":
|
||||
attribute.SetValue(obj, value.Convert<ushort>());
|
||||
return true;
|
||||
case "uint32":
|
||||
attribute.SetValue(obj, value.Convert<uint>());
|
||||
return true;
|
||||
case "single":
|
||||
attribute.SetValue(obj, (float)Math.Round(float.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 3));
|
||||
return true;
|
||||
case "double":
|
||||
attribute.SetValue(obj, Math.Round(double.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 3));
|
||||
return true;
|
||||
case "decimal":
|
||||
attribute.SetValue(obj, Math.Round(decimal.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 3));
|
||||
return true;
|
||||
default:
|
||||
throw new Exception($"Data type '{name}' not recognized, contact developer.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using Emgu.CV;
|
||||
using UVtools.Parser;
|
||||
|
||||
namespace UVtools.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Slicer file format representation interface
|
||||
/// </summary>
|
||||
public interface IFileFormat
|
||||
{
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// Gets the file format type
|
||||
/// </summary>
|
||||
FileFormat.FileFormatType FileType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the valid file extensions for this <see cref="FileFormat"/>
|
||||
/// </summary>
|
||||
FileExtension[] FileExtensions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the implemented file formats able to convert to
|
||||
/// </summary>
|
||||
Type[] ConvertToFormats { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available <see cref="FileFormat.PrintParameterModifier"/>
|
||||
/// </summary>
|
||||
FileFormat.PrintParameterModifier[] PrintParameterModifiers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file filter for open and save dialogs
|
||||
/// </summary>
|
||||
string FileFilter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all valid file extensions in "*.extension1;*.extension2" format
|
||||
/// </summary>
|
||||
|
||||
string FileFilterExtensionsOnly { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input file path loaded into this <see cref="FileFormat"/>
|
||||
/// </summary>
|
||||
string FileFullPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the thumbnails count present in this file format
|
||||
/// </summary>
|
||||
byte ThumbnailsCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of created thumbnails
|
||||
/// </summary>
|
||||
byte CreatedThumbnailsCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original thumbnail sizes
|
||||
/// </summary>
|
||||
System.Drawing.Size[] ThumbnailsOriginalSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the thumbnails for this <see cref="FileFormat"/>
|
||||
/// </summary>
|
||||
Mat Thumbnails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cached layers into compressed bytes
|
||||
/// </summary>
|
||||
LayerManager LayerManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image width resolution
|
||||
/// </summary>
|
||||
uint ResolutionX { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image height resolution
|
||||
/// </summary>
|
||||
uint ResolutionY { get; }
|
||||
|
||||
bool HaveAntiAliasing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AntiAliasing level
|
||||
/// </summary>
|
||||
byte AntiAliasing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Layer Height in mm
|
||||
/// </summary>
|
||||
float LayerHeight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Total Height in mm
|
||||
/// </summary>
|
||||
float TotalHeight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of layers present in this file
|
||||
/// </summary>
|
||||
uint LayerCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of initial layer count
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
ushort InitialLayerCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initial exposure time for <see cref="InitialLayerCount"/>
|
||||
/// </summary>
|
||||
float InitialExposureTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the normal layer exposure time
|
||||
/// </summary>
|
||||
float LayerExposureTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the speed in mm/min for the detracts
|
||||
/// </summary>
|
||||
float LiftSpeed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height in mm to retract between layers
|
||||
/// </summary>
|
||||
float LiftHeight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the speed in mm/min for the retracts
|
||||
/// </summary>
|
||||
float RetractSpeed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the estimate print time in seconds
|
||||
/// </summary>
|
||||
float PrintTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the estimate used material in ml
|
||||
/// </summary>
|
||||
float UsedMaterial { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the estimate material cost
|
||||
/// </summary>
|
||||
float MaterialCost { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the material name
|
||||
/// </summary>
|
||||
string MaterialName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the machine name
|
||||
/// </summary>
|
||||
string MachineName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GCode, returns null if not supported
|
||||
/// </summary>
|
||||
StringBuilder GCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get all configuration objects with properties and values
|
||||
/// </summary>
|
||||
object[] Configs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this file is valid to read
|
||||
/// </summary>
|
||||
bool IsValid { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>
|
||||
/// Clears all definitions and properties, it also dispose valid candidates
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Validate if a file is a valid <see cref="FileFormat"/>
|
||||
/// </summary>
|
||||
/// <param name="fileFullPath">Full file path</param>
|
||||
void FileValidation(string fileFullPath);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a extension is valid under the <see cref="FileFormat"/>
|
||||
/// </summary>
|
||||
/// <param name="extension">Extension to check</param>
|
||||
/// <param name="isFilePath">True if <see cref="extension"/> is a full file path, otherwise false for extension only</param>
|
||||
/// <returns>True if valid, otherwise false</returns>
|
||||
bool IsExtensionValid(string extension, bool isFilePath = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all valid file extensions in a specified format
|
||||
/// </summary>
|
||||
|
||||
string GetFileExtensions(string prepend = ".", string separator = ", ");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a thumbnail by it height or lower
|
||||
/// </summary>
|
||||
/// <param name="maxHeight">Max height allowed</param>
|
||||
/// <returns></returns>
|
||||
Mat GetThumbnail(uint maxHeight = 400);
|
||||
|
||||
/// <summary>
|
||||
/// Sets thumbnails from a list of thumbnails and clone them
|
||||
/// </summary>
|
||||
/// <param name="images"></param>
|
||||
void SetThumbnails(Mat[] images);
|
||||
|
||||
/// <summary>
|
||||
/// Sets all thumbnails the same image
|
||||
/// </summary>
|
||||
/// <param name="images">Image to set</param>
|
||||
void SetThumbnails(Mat images);
|
||||
|
||||
/// <summary>
|
||||
/// Encode to an output file
|
||||
/// </summary>
|
||||
/// <param name="fileFullPath">Output file</param>
|
||||
void Encode(string fileFullPath);
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Begin encode to an output file
|
||||
/// </summary>
|
||||
/// <param name="fileFullPath">Output file</param>
|
||||
//void BeginEncode(string fileFullPath);
|
||||
|
||||
/// <summary>
|
||||
/// Insert a layer image to be encoded
|
||||
/// </summary>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="layerIndex"></param>
|
||||
//void InsertLayerImageEncode(Image<L8> image, uint layerIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Finish the encoding procedure
|
||||
/// </summary>
|
||||
//void EndEncode();*/
|
||||
|
||||
/// <summary>
|
||||
/// Decode a slicer file
|
||||
/// </summary>
|
||||
/// <param name="fileFullPath"></param>
|
||||
void Decode(string fileFullPath);
|
||||
|
||||
/// <summary>
|
||||
/// Extract contents to a folder
|
||||
/// </summary>
|
||||
/// <param name="path">Path to folder where content will be extracted</param>
|
||||
/// <param name="genericConfigExtract"></param>
|
||||
/// <param name="genericLayersExtract"></param>
|
||||
void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true);
|
||||
|
||||
/// <summary>
|
||||
/// Get height in mm from layer height
|
||||
/// </summary>
|
||||
/// <param name="layerIndex"></param>
|
||||
/// <param name="realHeight"></param>
|
||||
/// <returns>The height in mm</returns>
|
||||
float GetHeightFromLayer(uint layerIndex, bool realHeight = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value for initial layer or normal layers based on layer index
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of value</typeparam>
|
||||
/// <param name="layerIndex">Layer index</param>
|
||||
/// <param name="initialLayerValue">Initial value</param>
|
||||
/// <param name="normalLayerValue">Normal value</param>
|
||||
/// <returns></returns>
|
||||
T GetInitialLayerValueOrNormal<T>(uint layerIndex, T initialLayerValue, T normalLayerValue);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value attributed to <see cref="FileFormat.PrintParameterModifier"/>
|
||||
/// </summary>
|
||||
/// <param name="modifier">Modifier to use</param>
|
||||
/// <returns>A value</returns>
|
||||
object GetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a property value attributed to <see cref="modifier"/>
|
||||
/// </summary>
|
||||
/// <param name="modifier">Modifier to use</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <returns>True if set, otherwise false = <see cref="modifier"/> not found</returns>
|
||||
bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a property value attributed to <see cref="modifier"/>
|
||||
/// </summary>
|
||||
/// <param name="modifier">Modifier to use</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <returns>True if set, otherwise false = <see cref="modifier"/> not found</returns>
|
||||
bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Saves current configuration on input file
|
||||
/// </summary>
|
||||
void Save();
|
||||
|
||||
/// <summary>
|
||||
/// Saves current configuration on a copy
|
||||
/// </summary>
|
||||
/// <param name="filePath">File path to save copy as, use null to overwrite active file (Same as <see cref="Save"/>)</param>
|
||||
void SaveAs(string filePath = null);
|
||||
|
||||
/// <summary>
|
||||
/// Converts this file type to another file type
|
||||
/// </summary>
|
||||
/// <param name="to">Target file format</param>
|
||||
/// <param name="fileFullPath">Output path file</param>
|
||||
/// <returns>True if convert succeed, otherwise false</returns>
|
||||
bool Convert(Type to, string fileFullPath);
|
||||
|
||||
/// <summary>
|
||||
/// Converts this file type to another file type
|
||||
/// </summary>
|
||||
/// <param name="to">Target file format</param>
|
||||
/// <param name="fileFullPath">Output path file</param>
|
||||
/// <returns>True if convert succeed, otherwise false</returns>
|
||||
bool Convert(FileFormat to, string fileFullPath);
|
||||
|
||||
/// <summary>
|
||||
/// Resizes layer images in x and y factor, starting at 1 = 100%
|
||||
/// </summary>
|
||||
/// <param name="startLayerIndex">Layer index to start</param>
|
||||
/// <param name="endLayerIndex">Layer index to end</param>
|
||||
/// <param name="x">X factor, starts at 1</param>
|
||||
/// <param name="y">Y factor, starts at 1</param>
|
||||
/// <param name="fade">Fade X/Y towards 100%</param>
|
||||
void Resize(uint startLayerIndex, uint endLayerIndex, float x, float y, bool fade);
|
||||
|
||||
/// <summary>
|
||||
/// Validate AntiAlias Level
|
||||
/// </summary>
|
||||
byte ValidateAntiAliasingLevel();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,853 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using Emgu.CV.Util;
|
||||
using UVtools.Core.Extensions;
|
||||
|
||||
namespace UVtools.Parser
|
||||
{
|
||||
#region LayerIssue Class
|
||||
|
||||
public class LayerIssue : IEnumerable<Point>
|
||||
{
|
||||
public enum IssueType : byte
|
||||
{
|
||||
Island,
|
||||
ResinTrap,
|
||||
TouchingBound,
|
||||
//HoleSandwich,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent layer
|
||||
/// </summary>
|
||||
public Layer Layer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the issue type associated
|
||||
/// </summary>
|
||||
public IssueType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pixels containing the issue
|
||||
/// </summary>
|
||||
public Point[] Pixels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding rectangle of the pixel area
|
||||
/// </summary>
|
||||
public Rectangle BoundingRectangle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the X coordinate for the first point, -1 if doesn't exists
|
||||
/// </summary>
|
||||
public int X => HaveValidPoint ? Pixels[0].X : -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Y coordinate for the first point, -1 if doesn't exists
|
||||
/// </summary>
|
||||
public int Y => HaveValidPoint ? Pixels[0].Y : -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XY point for first point
|
||||
/// </summary>
|
||||
public Point Point => HaveValidPoint ? Pixels[0] : new Point(-1, -1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of pixels on this issue
|
||||
/// </summary>
|
||||
public uint Size {
|
||||
get
|
||||
{
|
||||
if (Type == IssueType.ResinTrap && !BoundingRectangle.IsEmpty)
|
||||
{
|
||||
return (uint) (BoundingRectangle.Width * BoundingRectangle.Height);
|
||||
}
|
||||
|
||||
if (ReferenceEquals(Pixels, null)) return 0;
|
||||
return (uint) Pixels.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this issue have a valid start point to show
|
||||
/// </summary>
|
||||
public bool HaveValidPoint => !ReferenceEquals(Pixels, null) && Pixels.Length > 0;
|
||||
|
||||
public LayerIssue(Layer layer, IssueType type, Point[] pixels = null, Rectangle boundingRectangle = new Rectangle())
|
||||
{
|
||||
Layer = layer;
|
||||
Type = type;
|
||||
Pixels = pixels;
|
||||
BoundingRectangle = boundingRectangle;
|
||||
}
|
||||
|
||||
public Point this[uint index] => Pixels[index];
|
||||
|
||||
public Point this[int index] => Pixels[index];
|
||||
|
||||
public IEnumerator<Point> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<Point>)Pixels).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(Type)}: {Type}";
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region LayerHollowArea
|
||||
|
||||
public class LayerHollowArea : IEnumerable<Point>
|
||||
{
|
||||
public enum AreaType : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
Trap,
|
||||
Drain
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets area pixels
|
||||
/// </summary>
|
||||
public Point[] Contour { get; }
|
||||
|
||||
public System.Drawing.Rectangle BoundingRectangle { get; }
|
||||
|
||||
public AreaType Type { get; set; } = AreaType.Unknown;
|
||||
|
||||
public bool Processed { get; set; }
|
||||
|
||||
#region Indexers
|
||||
public Point this[uint index]
|
||||
{
|
||||
get => index < Contour.Length ? Contour[index] : Point.Empty;
|
||||
set => Contour[index] = value;
|
||||
}
|
||||
|
||||
public Point this[int index]
|
||||
{
|
||||
get => index < Contour.Length ? Contour[index] : Point.Empty;
|
||||
set => Contour[index] = value;
|
||||
}
|
||||
|
||||
public Point this[uint x, uint y]
|
||||
{
|
||||
get
|
||||
{
|
||||
for (uint i = 0; i < Contour.Length; i++)
|
||||
{
|
||||
if (Contour[i].X == x && Contour[i].Y == y) return Contour[i];
|
||||
}
|
||||
return Point.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public Point this[int x, int y] => this[(uint) x, (uint)y];
|
||||
|
||||
public Point this[Point point] => this[point.X, point.Y];
|
||||
|
||||
#endregion
|
||||
|
||||
public IEnumerator<Point> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<Point>)Contour).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public LayerHollowArea()
|
||||
{
|
||||
}
|
||||
|
||||
public LayerHollowArea(Point[] contour, System.Drawing.Rectangle boundingRectangle, AreaType type = AreaType.Unknown)
|
||||
{
|
||||
Contour = contour;
|
||||
BoundingRectangle = boundingRectangle;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Layer Class
|
||||
/// <summary>
|
||||
/// Represent a Layer
|
||||
/// </summary>
|
||||
public class Layer : IEquatable<Layer>, IEquatable<uint>
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent layer manager
|
||||
/// </summary>
|
||||
public LayerManager ParentLayerManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer index
|
||||
/// </summary>
|
||||
public uint Index { get; }
|
||||
|
||||
private byte[] _compressedBytes;
|
||||
/// <summary>
|
||||
/// Gets or sets layer image compressed data
|
||||
/// </summary>
|
||||
public byte[] CompressedBytes
|
||||
{
|
||||
get => LayerManager.DecompressLayer(_compressedBytes);
|
||||
set
|
||||
{
|
||||
_compressedBytes = LayerManager.CompressLayer(value);
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original filename, null if no filename attached with layer
|
||||
/// </summary>
|
||||
public string Filename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if layer has been modified
|
||||
/// </summary>
|
||||
public bool IsModified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a new image instance
|
||||
/// </summary>
|
||||
public Mat LayerMat
|
||||
{
|
||||
get
|
||||
{
|
||||
Mat mat = new Mat();
|
||||
CvInvoke.Imdecode(CompressedBytes, ImreadModes.Grayscale, mat);
|
||||
return mat;
|
||||
}
|
||||
set
|
||||
{
|
||||
using (var vector = new VectorOfByte())
|
||||
{
|
||||
CvInvoke.Imencode(".png", value, vector);
|
||||
CompressedBytes = vector.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new Brg image instance
|
||||
/// </summary>
|
||||
public Mat BrgMat
|
||||
{
|
||||
get {
|
||||
using (Mat image = LayerMat)
|
||||
{
|
||||
Mat mat = new Mat();
|
||||
CvInvoke.CvtColor(image, mat, ColorConversion.Gray2Bgr);
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public Layer(uint index, byte[] compressedBytes, string filename = null, LayerManager pararentLayerManager = null)
|
||||
{
|
||||
Index = index;
|
||||
CompressedBytes = compressedBytes;
|
||||
Filename = filename ?? $"Layer{index}.png";
|
||||
IsModified = false;
|
||||
ParentLayerManager = pararentLayerManager;
|
||||
}
|
||||
|
||||
public Layer(uint index, Mat layerMat, string filename = null, LayerManager pararentLayerManager = null) : this(index, new byte[0], filename, pararentLayerManager)
|
||||
{
|
||||
LayerMat = layerMat;
|
||||
IsModified = false;
|
||||
}
|
||||
|
||||
|
||||
public Layer(uint index, Stream stream, string filename = null, LayerManager pararentLayerManager = null) : this(index, stream.ToArray(), filename, pararentLayerManager)
|
||||
{ }
|
||||
#endregion
|
||||
|
||||
#region Equatables
|
||||
|
||||
public static bool operator ==(Layer obj1, Layer obj2)
|
||||
{
|
||||
return obj1.Equals(obj2);
|
||||
}
|
||||
|
||||
public static bool operator !=(Layer obj1, Layer obj2)
|
||||
{
|
||||
return !obj1.Equals(obj2);
|
||||
}
|
||||
|
||||
public static bool operator >(Layer obj1, Layer obj2)
|
||||
{
|
||||
return obj1.Index > obj2.Index;
|
||||
}
|
||||
|
||||
public static bool operator <(Layer obj1, Layer obj2)
|
||||
{
|
||||
return obj1.Index < obj2.Index;
|
||||
}
|
||||
|
||||
public static bool operator >=(Layer obj1, Layer obj2)
|
||||
{
|
||||
return obj1.Index >= obj2.Index;
|
||||
}
|
||||
|
||||
public static bool operator <=(Layer obj1, Layer obj2)
|
||||
{
|
||||
return obj1.Index <= obj2.Index;
|
||||
}
|
||||
|
||||
public bool Equals(uint other)
|
||||
{
|
||||
return Index == other;
|
||||
}
|
||||
|
||||
public bool Equals(Layer other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Equals(_compressedBytes, other._compressedBytes);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((Layer)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (_compressedBytes != null ? _compressedBytes.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
private sealed class IndexRelationalComparer : IComparer<Layer>
|
||||
{
|
||||
public int Compare(Layer x, Layer y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return 0;
|
||||
if (ReferenceEquals(null, y)) return 1;
|
||||
if (ReferenceEquals(null, x)) return -1;
|
||||
return x.Index.CompareTo(y.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public static IComparer<Layer> IndexComparer { get; } = new IndexRelationalComparer();
|
||||
#endregion
|
||||
|
||||
#region Formaters
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(Index)}: {Index}, {nameof(Filename)}: {Filename}, {nameof(IsModified)}: {IsModified}";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public Layer PreviousLayer()
|
||||
{
|
||||
if (ReferenceEquals(ParentLayerManager, null) || Index == 0)
|
||||
return null;
|
||||
|
||||
return ParentLayerManager[Index - 1];
|
||||
}
|
||||
|
||||
public Layer NextLayer()
|
||||
{
|
||||
if (ReferenceEquals(ParentLayerManager, null) || Index >= ParentLayerManager.Count - 1)
|
||||
return null;
|
||||
|
||||
return ParentLayerManager[Index + 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all islands start pixel location for this layer
|
||||
/// https://www.geeksforgeeks.org/find-number-of-islands/
|
||||
/// </summary>
|
||||
/// <returns><see cref="List{T}"/> holding all islands coordinates</returns>
|
||||
public List<LayerIssue> GetIssues(uint requiredPixelsToSupportIsland = 5)
|
||||
{
|
||||
if (requiredPixelsToSupportIsland == 0)
|
||||
requiredPixelsToSupportIsland = 1;
|
||||
|
||||
// These arrays are used to
|
||||
// get row and column numbers
|
||||
// of 8 neighbors of a given cell
|
||||
List<LayerIssue> result = new List<LayerIssue>();
|
||||
List<Point> pixels = new List<Point>();
|
||||
|
||||
|
||||
|
||||
var mat = LayerMat;
|
||||
var bytes = mat.GetBytes();
|
||||
|
||||
|
||||
|
||||
var previousLayerImage = PreviousLayer()?.LayerMat;
|
||||
byte[] previousBytes = previousLayerImage?.GetBytes();
|
||||
|
||||
|
||||
/*var nextLayerImage = NextLayer()?.Image;
|
||||
byte[] nextBytes = null;
|
||||
if (!ReferenceEquals(nextLayerImage, null))
|
||||
{
|
||||
if (nextLayerImage.TryGetSinglePixelSpan(out var nextPixelSpan))
|
||||
{
|
||||
nextBytes = MemoryMarshal.AsBytes(nextPixelSpan).ToArray();
|
||||
}
|
||||
}*/
|
||||
|
||||
// Make a bool array to
|
||||
// mark visited cells.
|
||||
// Initially all cells
|
||||
// are unvisited
|
||||
bool[,] visited = new bool[mat.Width, mat.Height];
|
||||
|
||||
// Initialize count as 0 and
|
||||
// traverse through the all
|
||||
// cells of given matrix
|
||||
//uint count = 0;
|
||||
|
||||
// Island checker
|
||||
sbyte[] rowNbr = { -1, -1, -1, 0, 0, 1, 1, 1 };
|
||||
sbyte[] colNbr = { -1, 0, 1, -1, 1, -1, 0, 1 };
|
||||
const uint minPixel = 10;
|
||||
const uint minPixelForSupportIsland = 200;
|
||||
int pixelIndex;
|
||||
uint islandSupportingPixels;
|
||||
if (Index > 0)
|
||||
{
|
||||
for (int y = 0; y < mat.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < mat.Width; x++)
|
||||
{
|
||||
pixelIndex = y * mat.Width + x;
|
||||
|
||||
/*if (bytes[pixelIndex] == 0 && previousBytes?[pixelIndex] == byte.MaxValue &&
|
||||
nextBytes?[pixelIndex] == byte.MaxValue)
|
||||
{
|
||||
result.Add(new LayerIssue(this, LayerIssue.IssueType.HoleSandwich, new []{new Point(x, y)}));
|
||||
}*/
|
||||
|
||||
if (bytes[pixelIndex] > minPixel && !visited[x, y])
|
||||
{
|
||||
// If a cell with value 1 is not
|
||||
// visited yet, then new island
|
||||
// found, Visit all cells in this
|
||||
// island and increment island count
|
||||
pixels.Clear();
|
||||
pixels.Add(new Point(x, y));
|
||||
islandSupportingPixels = previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0;
|
||||
|
||||
int minX = x;
|
||||
int maxX = x;
|
||||
int minY = y;
|
||||
int maxY = y;
|
||||
|
||||
int x2;
|
||||
int y2;
|
||||
|
||||
|
||||
Queue<Point> queue = new Queue<Point>();
|
||||
queue.Enqueue(new Point(x, y));
|
||||
// Mark this cell as visited
|
||||
visited[x, y] = true;
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var point = queue.Dequeue();
|
||||
y2 = point.Y;
|
||||
x2 = point.X;
|
||||
for (byte k = 0; k < 8; k++)
|
||||
{
|
||||
//if (isSafe(y2 + rowNbr[k], x2 + colNbr[k]))
|
||||
var tempy2 = y2 + rowNbr[k];
|
||||
var tempx2 = x2 + colNbr[k];
|
||||
pixelIndex = tempy2 * mat.Width + tempx2;
|
||||
if (tempy2 >= 0 &&
|
||||
tempy2 < mat.Height &&
|
||||
tempx2 >= 0 && tempx2 < mat.Width &&
|
||||
bytes[pixelIndex] >= minPixel &&
|
||||
!visited[tempx2, tempy2])
|
||||
{
|
||||
visited[tempx2, tempy2] = true;
|
||||
point = new Point(tempx2, tempy2);
|
||||
pixels.Add(point);
|
||||
queue.Enqueue(point);
|
||||
|
||||
minX = Math.Min(minX, tempx2);
|
||||
maxX = Math.Max(maxX, tempx2);
|
||||
minY = Math.Min(minY, tempy2);
|
||||
maxY = Math.Max(maxY, tempy2);
|
||||
|
||||
islandSupportingPixels += previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
//count++;
|
||||
|
||||
if (islandSupportingPixels >= requiredPixelsToSupportIsland)
|
||||
continue; // Not a island, bounding is strong
|
||||
if (islandSupportingPixels > 0 && pixels.Count < requiredPixelsToSupportIsland &&
|
||||
islandSupportingPixels >= Math.Max(1, pixels.Count / 2)) continue; // Not a island
|
||||
result.Add(new LayerIssue(this, LayerIssue.IssueType.Island, pixels.ToArray(), new Rectangle(minX, minY, maxX-minX, maxY-minY)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pixels.Clear();
|
||||
|
||||
// TouchingBounds Checker
|
||||
for (int x = 0; x < mat.Width; x++) // Check Top and Bottom bounds
|
||||
{
|
||||
if (bytes[x] >= 200) // Top
|
||||
{
|
||||
pixels.Add(new Point(x, 0));
|
||||
}
|
||||
|
||||
if (bytes[mat.Width * mat.Height - mat.Width + x] >= 200) // Bottom
|
||||
{
|
||||
pixels.Add(new Point(x, mat.Height-1));
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < mat.Height; y++) // Check Left and Right bounds
|
||||
{
|
||||
if (bytes[y * mat.Width] >= 200) // Left
|
||||
{
|
||||
pixels.Add(new Point(0, y));
|
||||
}
|
||||
|
||||
if (bytes[y * mat.Width + mat.Width - 1] >= 200) // Right
|
||||
{
|
||||
pixels.Add(new Point(mat.Width-1, y));
|
||||
}
|
||||
}
|
||||
|
||||
if (pixels.Count > 0)
|
||||
{
|
||||
result.Add(new LayerIssue(this, LayerIssue.IssueType.TouchingBound, pixels.ToArray()));
|
||||
}
|
||||
|
||||
pixels.Clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Layer Clone()
|
||||
{
|
||||
return new Layer(Index, CompressedBytes, Filename, ParentLayerManager);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void Resize(float newWidth, float newHeight)
|
||||
{
|
||||
using (var mat = LayerMat)
|
||||
{
|
||||
using (var resizedMat = new Mat())
|
||||
{
|
||||
int width = (int)(mat.Width * newWidth);
|
||||
int height = (int)(mat.Height * newHeight);
|
||||
Point location = new Point(mat.Width / 2 - width / 2, mat.Height / 2 - height / 2);
|
||||
|
||||
CvInvoke.Resize(mat, resizedMat, new Size(width, height));
|
||||
mat.SetTo(new MCvScalar(0));
|
||||
resizedMat.CopyTo(mat);
|
||||
LayerMat = mat;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region LayerManager Class
|
||||
public class LayerManager : IEnumerable<Layer>
|
||||
{
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// Layers List
|
||||
/// </summary>
|
||||
public Layer[] Layers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layers count
|
||||
/// </summary>
|
||||
public uint Count => (uint) Layers.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if any layer got modified, otherwise false
|
||||
/// </summary>
|
||||
public bool IsModified
|
||||
{
|
||||
get
|
||||
{
|
||||
for (uint i = 0; i < Count; i++)
|
||||
{
|
||||
if (Layers[i].IsModified) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public LayerManager(uint layerCount)
|
||||
{
|
||||
Layers = new Layer[layerCount];
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Indexers
|
||||
public Layer this[uint index]
|
||||
{
|
||||
get => Layers[index];
|
||||
set => AddLayer(index, value);
|
||||
}
|
||||
|
||||
public Layer this[int index]
|
||||
{
|
||||
get => Layers[index];
|
||||
set => AddLayer((uint) index, value);
|
||||
}
|
||||
|
||||
public Layer this[long index]
|
||||
{
|
||||
get => Layers[index];
|
||||
set => AddLayer((uint) index, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Numerators
|
||||
public IEnumerator<Layer> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<Layer>)Layers).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Static Methods
|
||||
/// <summary>
|
||||
/// Compress a layer from a <see cref="Stream"/>
|
||||
/// </summary>
|
||||
/// <param name="input"><see cref="Stream"/> to compress</param>
|
||||
/// <returns>Compressed byte array</returns>
|
||||
public static byte[] CompressLayer(Stream input)
|
||||
{
|
||||
return CompressLayer(input.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress a layer from a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">byte array to compress</param>
|
||||
/// <returns>Compressed byte array</returns>
|
||||
public static byte[] CompressLayer(byte[] input)
|
||||
{
|
||||
return input;
|
||||
/*using (MemoryStream output = new MemoryStream())
|
||||
{
|
||||
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal))
|
||||
{
|
||||
dstream.Write(input, 0, input.Length);
|
||||
}
|
||||
return output.ToArray();
|
||||
}*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompress a layer from a byte array
|
||||
/// </summary>
|
||||
/// <param name="input">byte array to decompress</param>
|
||||
/// <returns>Decompressed byte array</returns>
|
||||
public static byte[] DecompressLayer(byte[] input)
|
||||
{
|
||||
return input;
|
||||
/*using (MemoryStream ms = new MemoryStream(input))
|
||||
{
|
||||
using (MemoryStream output = new MemoryStream())
|
||||
{
|
||||
using (DeflateStream dstream = new DeflateStream(ms, CompressionMode.Decompress))
|
||||
{
|
||||
dstream.CopyTo(output);
|
||||
}
|
||||
return output.ToArray();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Add a layer
|
||||
/// </summary>
|
||||
/// <param name="index">Layer index</param>
|
||||
/// <param name="layer">Layer to add</param>
|
||||
public void AddLayer(uint index, Layer layer)
|
||||
{
|
||||
Layers[index] = layer;
|
||||
layer.ParentLayerManager = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get layer given index
|
||||
/// </summary>
|
||||
/// <param name="index">Layer index</param>
|
||||
/// <returns></returns>
|
||||
public Layer GetLayer(uint index)
|
||||
{
|
||||
return Layers[index];
|
||||
}
|
||||
|
||||
public ConcurrentDictionary<uint, List<LayerIssue>> GetAllIssues()
|
||||
{
|
||||
var result = new ConcurrentDictionary<uint, List<LayerIssue>>();
|
||||
|
||||
Parallel.ForEach(this, layer =>
|
||||
{
|
||||
var issues = layer.GetIssues();
|
||||
if (issues.Count > 0)
|
||||
{
|
||||
if (!result.TryAdd(layer.Index, issues))
|
||||
{
|
||||
throw new AccessViolationException("Error while trying to add an issue to the dictionary, please try again.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*const byte minPixel = 50;
|
||||
sbyte[] rowNbr = { -1, -1, -1, 0, 0, 1, 1, 1 };
|
||||
sbyte[] colNbr = { -1, 0, 1, -1, 1, -1, 0, 1 };
|
||||
|
||||
int pixelIndex;
|
||||
for (uint layerindex = 0; layerindex < Count; layerindex++)
|
||||
{
|
||||
var image = this[layerindex].Image;
|
||||
byte[] bytes = null;
|
||||
if (image.TryGetSinglePixelSpan(out var pixelSpan))
|
||||
{
|
||||
bytes = MemoryMarshal.AsBytes(pixelSpan).ToArray();
|
||||
}
|
||||
|
||||
bool[,] visited = new bool[image.Width, image.Height];
|
||||
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
pixelIndex = y * image.Width + x;
|
||||
if (bytes[pixelIndex] > minPixel && !visited[y, x])
|
||||
{
|
||||
Queue<Point> queue = new Queue<Point>();
|
||||
queue.Enqueue(new Point(x, y));
|
||||
// Mark this cell as visited
|
||||
visited[x, y] = true;
|
||||
|
||||
var x2 = x;
|
||||
var y2 = y;
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var point = queue.Dequeue();
|
||||
y2 = point.Y;
|
||||
x2 = point.X;
|
||||
for (byte k = 0; k < 8; k++)
|
||||
{
|
||||
//if (isSafe(y2 + rowNbr[k], x2 + colNbr[k]))
|
||||
var tempy2 = y2 + rowNbr[k];
|
||||
var tempx2 = x2 + colNbr[k];
|
||||
pixelIndex = tempy2 * image.Width + tempx2;
|
||||
if (tempy2 >= 0 &&
|
||||
tempy2 < image.Height &&
|
||||
tempx2 >= 0 && tempx2 < image.Width &&
|
||||
bytes[pixelIndex] >= minPixel &&
|
||||
!visited[tempx2, tempy2])
|
||||
{
|
||||
visited[tempx2, tempy2] = true;
|
||||
point = new Point(tempx2, tempy2);
|
||||
pixels.Add(point);
|
||||
queue.Enqueue(point);
|
||||
|
||||
islandSupportingPixels += previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desmodify all layers
|
||||
/// </summary>
|
||||
public void Desmodify()
|
||||
{
|
||||
for (uint i = 0; i < Count; i++)
|
||||
{
|
||||
Layers[i].IsModified = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clone this object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public LayerManager Clone()
|
||||
{
|
||||
LayerManager layerManager = new LayerManager(Count);
|
||||
foreach (var layer in this)
|
||||
{
|
||||
layerManager[layer.Index] = layer.Clone();
|
||||
}
|
||||
|
||||
return layerManager;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<Company>PTRTECH</Company>
|
||||
<Authors>Tiago Conceição</Authors>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
|
||||
<Description>MSLA/DLP, file analysis, repair, conversion and manipulation</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BinarySerializer" Version="8.5.1" />
|
||||
<PackageReference Include="Emgu.CV" Version="4.3.0.3890" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -6,12 +6,16 @@
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using UVtools.Parser;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace UVtools.GUI.Extensions
|
||||
{
|
||||
@@ -40,6 +44,13 @@ namespace UVtools.GUI.Extensions
|
||||
};
|
||||
}
|
||||
|
||||
public static Mat ToEmguMat(this Image<L8> image)
|
||||
{
|
||||
var mat = new Mat(new System.Drawing.Size(image.Width, image.Height), DepthType.Cv8U, 1);
|
||||
mat.SetTo(Helpers.ImageL8ToBytes(image));
|
||||
return mat;
|
||||
}
|
||||
|
||||
/*public static Image<TPixel> ToImageSharpImage<TPixel>(this System.Drawing.Bitmap bitmap) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
|
||||
+11
-6
@@ -1715,13 +1715,19 @@ namespace UVtools.GUI
|
||||
|
||||
if (tsLayerImageLayerOutline.Checked)
|
||||
{
|
||||
Image<Gray, byte> greyscale = ActualLayerImage.ToEmguImage();
|
||||
Image<Gray, byte> greyscale = ActualLayerImage.ToEmguImage(); // 10ms
|
||||
SlicerFile[ActualLayer].Image = ActualLayerImage; // 127ms
|
||||
var mat = ActualLayerImage.ToEmguMat(); // 4ms
|
||||
|
||||
var bytes = new VectorOfByte();
|
||||
CvInvoke.Imencode(".png", mat, bytes); // 9ms
|
||||
CvInvoke.Imdecode(bytes, ImreadModes.Grayscale, mat); // 10ms
|
||||
Debug.WriteLine(CvInvoke.UseOpenCL);
|
||||
#if DEBUG
|
||||
greyscale = greyscale.ThresholdBinary(new Gray(254), new Gray(255));
|
||||
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
|
||||
Mat hierarchy = new Mat();
|
||||
|
||||
|
||||
CvInvoke.FindContours(greyscale, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple);
|
||||
|
||||
/*
|
||||
@@ -1735,10 +1741,9 @@ namespace UVtools.GUI
|
||||
{
|
||||
if ((int)arr.GetValue(0, i, 3) >= 0) continue;
|
||||
var r = CvInvoke.BoundingRectangle(contours[i]);
|
||||
CvInvoke.Rectangle(greyscale, r, new MCvScalar(80), 2);
|
||||
|
||||
greyscale.Draw(contours, i, new Gray(125), -1);
|
||||
|
||||
//CvInvoke.Rectangle(greyscale, r, new MCvScalar(80), 2);
|
||||
greyscale.Draw(contours, -1, new Gray(125), -1);
|
||||
//CvInvoke.DrawContours(mat, contours, -1, new MCvScalar(255), -1);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@@ -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.5.2.1")]
|
||||
[assembly: AssemblyFileVersion("0.5.2.1")]
|
||||
[assembly: AssemblyVersion("0.5.2.2")]
|
||||
[assembly: AssemblyFileVersion("0.5.2.2")]
|
||||
|
||||
@@ -9,7 +9,6 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
+18
-11
@@ -586,10 +586,13 @@ namespace UVtools.Parser
|
||||
{
|
||||
KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex);
|
||||
EncodedRle = kr.Read(rawData).ToArray();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
EncodedRle = rawData.ToArray();
|
||||
}
|
||||
|
||||
EncodedRle = rawData.ToArray();
|
||||
DataSize = (uint) EncodedRle.Length;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -603,8 +606,8 @@ namespace UVtools.Parser
|
||||
|
||||
public class KeyRing
|
||||
{
|
||||
public ulong Init { get; }
|
||||
public ulong Key { get; private set; }
|
||||
public uint Init { get; }
|
||||
public uint Key { get; private set; }
|
||||
public uint Index { get; private set; }
|
||||
|
||||
public KeyRing(uint seed, uint layerIndex)
|
||||
@@ -819,11 +822,12 @@ namespace UVtools.Parser
|
||||
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
|
||||
{
|
||||
LayerData layerData = LayersDefinitions[layerIndex];
|
||||
LayerData layerDataHash = null;
|
||||
|
||||
if (HeaderSettings.EncryptionKey == 0)
|
||||
{
|
||||
string hash = Helpers.ComputeSHA1Hash(layerData.EncodedRle);
|
||||
if (LayersHash.TryGetValue(hash, out var layerDataHash))
|
||||
if (LayersHash.TryGetValue(hash, out layerDataHash))
|
||||
{
|
||||
layerData.DataAddress = layerDataHash.DataAddress;
|
||||
layerData.DataSize = layerDataHash.DataSize;
|
||||
@@ -831,15 +835,18 @@ namespace UVtools.Parser
|
||||
else
|
||||
{
|
||||
LayersHash.Add(hash, layerData);
|
||||
layerData.DataAddress = layerDataCurrentOffset;
|
||||
layerData.DataSize = (uint)layerData.EncodedRle.Length;
|
||||
|
||||
outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin);
|
||||
layerDataCurrentOffset += outputFile.WriteBytes(layerData.EncodedRle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ReferenceEquals(layerDataHash, null))
|
||||
{
|
||||
layerData.DataAddress = layerDataCurrentOffset;
|
||||
|
||||
outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin);
|
||||
layerDataCurrentOffset += outputFile.WriteBytes(layerData.EncodedRle);
|
||||
}
|
||||
|
||||
|
||||
LayersDefinitions[layerIndex] = layerData;
|
||||
|
||||
outputFile.Seek(currentOffset, SeekOrigin.Begin);
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
|
||||
<PackageIcon></PackageIcon>
|
||||
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
|
||||
<AssemblyVersion>0.5.2.1</AssemblyVersion>
|
||||
<FileVersion>0.5.2.1</FileVersion>
|
||||
<Version>0.5.2.1</Version>
|
||||
<AssemblyVersion>0.5.2.2</AssemblyVersion>
|
||||
<FileVersion>0.5.2.2</FileVersion>
|
||||
<Version>0.5.2.2</Version>
|
||||
<Description>MSLA/DLP, file analysis, repair, conversion and manipulation</Description>
|
||||
<PackageId>UVtoolsParser</PackageId>
|
||||
<PackageId>UVtools.Parser</PackageId>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<Product>UVtoolsParser</Product>
|
||||
<Product>UVtools.Parser</Product>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+10
@@ -14,6 +14,8 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "UVtools.InstallerMM", "UVto
|
||||
EndProject
|
||||
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "UVtools.Installer", "UVtools.Installer\UVtools.Installer.wixproj", "{41224896-08E9-4F22-9E56-5F9D46DEC7D2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UVtools.Core", "UVtools.Core\UVtools.Core.csproj", "{7C9927F8-132E-4A37-B894-440E0FD5AA3D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -50,6 +52,14 @@ Global
|
||||
{41224896-08E9-4F22-9E56-5F9D46DEC7D2}.Release|Any CPU.ActiveCfg = Release|x86
|
||||
{41224896-08E9-4F22-9E56-5F9D46DEC7D2}.Release|x86.ActiveCfg = Release|x86
|
||||
{41224896-08E9-4F22-9E56-5F9D46DEC7D2}.Release|x86.Build.0 = Release|x86
|
||||
{7C9927F8-132E-4A37-B894-440E0FD5AA3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C9927F8-132E-4A37-B894-440E0FD5AA3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C9927F8-132E-4A37-B894-440E0FD5AA3D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7C9927F8-132E-4A37-B894-440E0FD5AA3D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7C9927F8-132E-4A37-B894-440E0FD5AA3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C9927F8-132E-4A37-B894-440E0FD5AA3D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7C9927F8-132E-4A37-B894-440E0FD5AA3D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7C9927F8-132E-4A37-B894-440E0FD5AA3D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Reference in New Issue
Block a user