diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66823e9..29f0e14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,67 @@
# Changelog
+## 12/03/2022 - v3.0.0
+
+- **(Add) Suggestions:**
+ - A new module that detect bad or parameters out of a defined range and suggest a change on the file, those can be auto applied if configured to do so
+ - **Avaliable suggestions:**
+ - **Bottom layer count:** Bottom layers should be kept to a minimum, usually from 2 to 3, it function is to provide a good adhesion to the first layer on the build plate, using a high count have disadvantages.
+ - **Wait time before cure:** Rest some time before cure the layer is crucial to let the resin settle after the lift sequence and allow some time for the arm settle at the correct Z position as the resin will offer some resistance and push the structure.
+ This lead to better quality with more successful prints, less lamination problems, better first layers with more success of stick to the build plate and less elephant foot effect.
+ - **Wait time after cure:** Rest some time after cure the layer and before the lift sequence can be important to allow the layer to cooldown a bit and detach better from the FEP.
+ - **Layer height:** Using the right layer height is important to get successful prints:
+ Thin layers may cause problems on adhesion, lamination, will print much slower and have no real visual benefits.
+ Thick layers may not fully cure no matter the exposure time you use, causing lamination and other hazards. Read your resin dtasheet to know the limits.
+ Using layer height with too many decimal digits may produce a wrong positioning due stepper step loss and/or Z axis quality.
+- **Core:**
+ - Convert the project to Nullable aware and "null-safe"
+- **File Formats:**
+ - (Add) `Volume` property to get the total model volume
+ - (Add) `SanitizeLayers` method to reassign indexes and force attribute parent file
+ - (Improvement) Merge `LayerManager` into `FileFormat` and cleanup: This affects the whole project and external scripts.
+ If using scripts please update them, search for `.LayerManager.` and replace by `.`
+ - (Change) Chitubox encrypted format can now be saved as normal
+ - (Fix) Converted files layers was pointing to the source file and related to it
+- **Layers:**
+ - (Add) Methods: `ResetParameters`, `CopyParametersTo`, `CopyExposureTo`, `CopyWaitTimesTo`
+ - (Improvement) `IsBottomLayer` property will also return true when the index is inside bottom layer count
+- **Scripting:**
+ - (Add) Configuration variable: `MinimumVersionToRun` - Sets the minimum version able to run the script
+ - (Improvement) Allow run scripts written in C# 10 with the new namespace; style as well as nullables methods
+ - (Improvement) Convert scripts to use Nullable code
+- **UI:**
+ - (Add) Fluent Dark theme
+ - (Add) Default Light theme
+ - (Add) Default Dark theme
+ - (Change) Use fontawesome and material design to render the icons instead of static png images
+ - (Change) Some icons
+ - (Change) Move log tab to clipboard tab
+ - (Change) Tooltip overlay default color
+ - (Improvement) Windows position for tool windows, sometimes framework can return negative values affecting positions, now limits to 0 (#387)
+ - (Fix) Center image icon for layer action button
+ - (Fix) Center image icon for save layer image button
+- **Tools:**
+ - (Add) Layer re-height: Offset mode, change layers position by a defined offset (#423)
+ - (Improvement) Rotate: Unable to use an angle of 0
+ - (Improvement) Remove layers: Will not recalcualte and reset properties of layers anymore, allowing removing layers on dynamic layer height models and others
+ - (Improvement) Clone layers: Will not recalcualte and reset properties of layers anymore, allowing cloning layers on dynamic layer height models and others
+ - (Fix) Exposure time finder: Very small printers may not print the stock object as it is configured, lead to a unknown error while generating the test. It will now show a better error message and advice a solution (#426)
+- **Terminal:**
+ - (Add) More default namespaces
+ - (Improvement) Set a MinHeight for the rows to prevent spliter from eat the elements
+ - (Change) Set working space to the MainWindow instead of TerminalWindow
+- **(Upgrade) .NET from 5.0.14 to 6.0.3**
+ - This brings big performance improvements, better JIT, faster I/O operations and others
+ - Read more: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6
+ - Due this macOS requirement starts at 10.15 (Catalina)
+ - Read more: https://github.com/dotnet/core/blob/main/release-notes/6.0/supported-os.md
+- (Add) Native support for MacOS ARM64 architecture (Mac M1 and upcomming Mac's) (#187)
+- (Exchange) Dependency Newtonsoft Json by System.Text.Json to parse the json documents
+- (Remove) Dependency: MoreLinq
+- (Remove) "Automations - Light-off delay" in favor of new suggestion "wait time before cure" module
+- (Fix) File - Send to: Winrar or 7zip have a wrong extension on the list (uvt) when should be (uvj)
+- (Upgrade) AvaloniaUI from 0.10.12 to 0.10.13
+
## 21/02/2022 - v2.29.0
- **File formats:**
@@ -9,7 +71,7 @@
- (Add) SL1: Keyword `TransitionLayerCount_xxx` - Sets the number of transition layers
- (Improvement) CTB, PHZ, FDG: Implement the `ModifiedTimestampMinutes` field, it was the MysteriousId before as an unknown field
- (Fix) CWS: Open in partial mode will cause an exception and prevent file from load
-- **CCode:**
+- **GCode:**
- (Add) Allow inverse lifts to work as an retract
- (Fix) Parsing of WaitTimeAfterLift was incorrect when lacking a lift sequence
- (Fix) Layers lacking an exposure time was defaulting to global time, now defaults to 0
@@ -19,7 +81,7 @@
- (Add) Information: Raise Layer count equivalence
- (Add) Information: Additional lifts to be generated
- (Add) Option: Ensure the last layer - If enabled, it will generate an obligatory layer to cover the last layer
- - (Improvement) Optimize lift for virtual layer mode, allowing set a slow and fast lift / retract by using another virtual layyer to emulate a lift
+ - (Improvement) Optimize lift for virtual layer mode, allowing set a slow and fast lift / retract by using another virtual layer to emulate a lift
- (Improvement) Allow to define slow and fast speed for virtual layer mode even if TSMC isn't supported
- (Add) Fade exposure time: Setting 'Disable firmware transition layers' - Attempt to disable firmware strict transition layers in favor of this tool
- (Add) Calibration tests: Attempt to auto disable the firmware transifiton layers
@@ -769,6 +831,7 @@
- (Fix) macOS: Include libusb-1.0.0.dylib
- Note: `brew install libusb` still required
- **UI:**
+ - (Add) Shorcuts: Arrow up and down to navigate layers while layer image is on focus
- (Fix) Refresh gcode does not update text on UI for ZIP, CWS, ZCODEX files
- (Fix) Operations: Import a .uvtop file by drag and drop into the UI would not load the layer range
- (Change) When convert a file, the result dialog will have Yes, No and Cancel actions, where No will open the converted file on current window, while Cancel will not perform any action (The old No behaviour)
diff --git a/CREDITS.md b/CREDITS.md
index bcdabe4..7f9976e 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -68,4 +68,6 @@
- Riccardo Kocmann
- Joshua Pitts
- Tim Anderson
-- Sakari Toivonen
\ No newline at end of file
+- Sakari Toivonen
+- Ed Wagaman
+- Marcin Chomiczuk
\ No newline at end of file
diff --git a/README.md b/README.md
index 119719b..3c94132 100644
--- a/README.md
+++ b/README.md
@@ -196,7 +196,7 @@ The UVtools executable allow to set some arguments to do special functions:
- Type: appwiz.cpl (and press Enter key)
- Click on: Turn Windows features on or off
- Check the "Media Extensions" and click Ok
-
+
1. 4GB RAM or higher
1. 1920 x 1080 @ 100% scale as minimum resolution
@@ -215,7 +215,7 @@ sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y apt-transport-https
-dotnet-runtime-5.0
+dotnet-runtime-6.0
!-->
```bash
@@ -348,7 +348,7 @@ this process is very slow but only need to run once. Open a terminal on any fold
```bash
sudo yum update -y
sudo yum groupinstall -y "Development Tools" "Development Libraries"
-sudo yum install -y cmake gcc-c++ dotnet-sdk-5.0 gtk3-devel gstreamer1-devel ffmpeg ffmpeg-devel libdc1394 libv4l-devel cmake-gui ocl-icd-devel freeglut libgeotiff libusb
+sudo yum install -y cmake gcc-c++ dotnet-sdk-6.0 gtk3-devel gstreamer1-devel ffmpeg ffmpeg-devel libdc1394 libv4l-devel cmake-gui ocl-icd-devel freeglut libgeotiff libusb
git clone https://github.com/emgucv/emgucv emgucv
cd emgucv
git submodule update --init --recursive
@@ -370,11 +370,11 @@ anyone with same system version can make use of it without the need of the compi
## Mac
-1. macOS 10.13 High Sierra
+1. macOS 10.15 Catalina or higher
1. 4GB RAM or higher
diff --git a/UVtools.AvaloniaControls/AdvancedImageBox.axaml.cs b/UVtools.AvaloniaControls/AdvancedImageBox.axaml.cs
index 6ff899d..f8c88c2 100644
--- a/UVtools.AvaloniaControls/AdvancedImageBox.axaml.cs
+++ b/UVtools.AvaloniaControls/AdvancedImageBox.axaml.cs
@@ -27,2178 +27,2185 @@ using Pen = Avalonia.Media.Pen;
using Point = Avalonia.Point;
using Size = Avalonia.Size;
-namespace UVtools.AvaloniaControls
-{
- public class AdvancedImageBox : UserControl
- {
- #region Bindable Base
- ///
- /// Multicast event for property change notifications.
- ///
- private PropertyChangedEventHandler _propertyChanged;
+namespace UVtools.AvaloniaControls;
- public new event PropertyChangedEventHandler PropertyChanged
+public class AdvancedImageBox : UserControl
+{
+ #region Bindable Base
+ ///
+ /// Multicast event for property change notifications.
+ ///
+ private PropertyChangedEventHandler? _propertyChanged;
+
+ public new event PropertyChangedEventHandler PropertyChanged
+ {
+ add => _propertyChanged += value;
+ remove => _propertyChanged -= value;
+ }
+ protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string? propertyName = null)
+ {
+ if (EqualityComparer.Default.Equals(field, value)) return false;
+ field = value;
+ RaisePropertyChanged(propertyName);
+ return true;
+ }
+
+
+ protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ }
+
+ ///
+ /// Notifies listeners that a property value has changed.
+ ///
+ ///
+ /// Name of the property used to notify listeners. This
+ /// value is optional and can be provided automatically when invoked from compilers
+ /// that support .
+ ///
+ protected void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ var e = new PropertyChangedEventArgs(propertyName);
+ OnPropertyChanged(e);
+ _propertyChanged?.Invoke(this, e);
+ }
+ #endregion
+
+ #region Sub Classes
+
+ ///
+ /// Represents available levels of zoom in an control
+ ///
+ public class ZoomLevelCollection : IList
+ {
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ZoomLevelCollection()
{
- add => _propertyChanged += value;
- remove => _propertyChanged -= value;
+ List = new SortedList();
}
- protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string propertyName = null)
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The default values to populate the collection with.
+ /// Thrown if the collection parameter is null
+ public ZoomLevelCollection(IEnumerable collection)
+ : this()
{
- if (EqualityComparer.Default.Equals(field, value)) return false;
- field = value;
- RaisePropertyChanged(propertyName);
+ if (collection == null)
+ {
+ throw new ArgumentNullException(nameof(collection));
+ }
+
+ AddRange(collection);
+ }
+
+ #endregion
+
+ #region Public Class Properties
+
+ ///
+ /// Returns the default zoom levels
+ ///
+ public static ZoomLevelCollection Default =>
+ new(new[] {
+ 7, 10, 15, 20, 25, 30, 50, 70, 100, 150, 200, 300, 400, 500, 600, 700, 800, 1200, 1600, 3200
+ });
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the number of elements contained in the .
+ ///
+ ///
+ /// The number of elements contained in the .
+ ///
+ public int Count => List.Count;
+
+ ///
+ /// Gets a value indicating whether the is read-only.
+ ///
+ /// true if this instance is read only; otherwise, false.
+ /// true if the is read-only; otherwise, false.
+ ///
+ public bool IsReadOnly => false;
+
+ ///
+ /// Gets or sets the zoom level at the specified index.
+ ///
+ /// The index.
+ public int this[int index]
+ {
+ get => List.Values[index];
+ set
+ {
+ List.RemoveAt(index);
+ Add(value);
+ }
+ }
+
+ #endregion
+
+ #region Protected Properties
+
+ ///
+ /// Gets or sets the backing list.
+ ///
+ protected SortedList List { get; set; }
+
+ #endregion
+
+ #region Public Members
+
+ ///
+ /// Adds an item to the .
+ ///
+ /// The object to add to the .
+ public void Add(int item)
+ {
+ List.Add(item, item);
+ }
+
+ ///
+ /// Adds a range of items to the .
+ ///
+ /// The items to add to the collection.
+ /// Thrown if the collection parameter is null.
+ public void AddRange(IEnumerable collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException(nameof(collection));
+ }
+
+ foreach (int value in collection)
+ {
+ Add(value);
+ }
+ }
+
+ ///
+ /// Removes all items from the .
+ ///
+ public void Clear()
+ {
+ List.Clear();
+ }
+
+ ///
+ /// Determines whether the contains a specific value.
+ ///
+ /// The object to locate in the .
+ /// true if is found in the ; otherwise, false.
+ public bool Contains(int item)
+ {
+ return List.ContainsKey(item);
+ }
+
+ ///
+ /// Copies a range of elements this collection into a destination .
+ ///
+ /// The that receives the data.
+ /// A 64-bit integer that represents the index in the at which storing begins.
+ public void CopyTo(int[] array, int arrayIndex)
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ array[arrayIndex + i] = List.Values[i];
+ }
+ }
+
+ ///
+ /// Finds the index of a zoom level matching or nearest to the specified value.
+ ///
+ /// The zoom level.
+ public int FindNearest(int zoomLevel)
+ {
+ int nearestValue = List.Values[0];
+ int nearestDifference = Math.Abs(nearestValue - zoomLevel);
+ for (int i = 1; i < Count; i++)
+ {
+ int value = List.Values[i];
+ int difference = Math.Abs(value - zoomLevel);
+ if (difference < nearestDifference)
+ {
+ nearestValue = value;
+ nearestDifference = difference;
+ }
+ }
+ return nearestValue;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// A that can be used to iterate through the collection.
+ public IEnumerator GetEnumerator()
+ {
+ return List.Values.GetEnumerator();
+ }
+
+ ///
+ /// Determines the index of a specific item in the .
+ ///
+ /// The object to locate in the .
+ /// The index of if found in the list; otherwise, -1.
+ public int IndexOf(int item)
+ {
+ return List.IndexOfKey(item);
+ }
+
+ ///
+ /// Not implemented.
+ ///
+ /// The index.
+ /// The item.
+ /// Not implemented
+ public void Insert(int index, int item)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Returns the next increased zoom level for the given current zoom.
+ ///
+ /// The current zoom level.
+ /// The next matching increased zoom level for the given current zoom if applicable, otherwise the nearest zoom.
+ public int NextZoom(int zoomLevel)
+ {
+ var index = IndexOf(FindNearest(zoomLevel));
+ if (index < Count - 1)
+ {
+ index++;
+ }
+
+ return this[index];
+ }
+
+ ///
+ /// Returns the next decreased zoom level for the given current zoom.
+ ///
+ /// The current zoom level.
+ /// The next matching decreased zoom level for the given current zoom if applicable, otherwise the nearest zoom.
+ public int PreviousZoom(int zoomLevel)
+ {
+ var index = IndexOf(FindNearest(zoomLevel));
+ if (index > 0)
+ {
+ index--;
+ }
+
+ return this[index];
+ }
+
+ ///
+ /// Removes the first occurrence of a specific object from the .
+ ///
+ /// The object to remove from the .
+ /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original .
+ public bool Remove(int item)
+ {
+ return List.Remove(item);
+ }
+
+ ///
+ /// Removes the element at the specified index of the .
+ ///
+ /// The zero-based index of the element to remove.
+ public void RemoveAt(int index)
+ {
+ List.RemoveAt(index);
+ }
+
+ ///
+ /// Copies the elements of the to a new array.
+ ///
+ /// An array containing copies of the elements of the .
+ public int[] ToArray()
+ {
+ int[] results;
+
+ results = new int[Count];
+ CopyTo(results, 0);
+
+ return results;
+ }
+
+ #endregion
+
+ #region IList Members
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ /// An object that can be used to iterate through the collection.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+ }
+
+ #endregion
+
+ #region Enums
+
+ ///
+ /// Determines the sizing mode of an image hosted in an control.
+ ///
+ public enum SizeModes : byte
+ {
+ ///
+ /// The image is displayed according to current zoom and scroll properties.
+ ///
+ Normal,
+
+ ///
+ /// The image is stretched to fill the client area of the control.
+ ///
+ Stretch,
+
+ ///
+ /// The image is stretched to fill as much of the client area of the control as possible, whilst retaining the same aspect ratio for the width and height.
+ ///
+ Fit
+ }
+
+ [Flags]
+ public enum MouseButtons : byte
+ {
+ None = 0,
+ LeftButton = 1,
+ MiddleButton = 2,
+ RightButton = 4
+ }
+
+ ///
+ /// Describes the zoom action occurring
+ ///
+ [Flags]
+ public enum ZoomActions : byte
+ {
+ ///
+ /// No action.
+ ///
+ None = 0,
+
+ ///
+ /// The control is increasing the zoom.
+ ///
+ ZoomIn = 1,
+
+ ///
+ /// The control is decreasing the zoom.
+ ///
+ ZoomOut = 2,
+
+ ///
+ /// The control zoom was reset.
+ ///
+ ActualSize = 4
+ }
+
+ public enum SelectionModes
+ {
+ ///
+ /// No selection.
+ ///
+ None,
+
+ ///
+ /// Rectangle selection.
+ ///
+ Rectangle,
+
+ ///
+ /// Zoom selection.
+ ///
+ Zoom
+ }
+
+ #endregion
+
+ #region UI Controls
+ public ScrollBar HorizontalScrollBar { get; }
+ public ScrollBar VerticalScrollBar { get; }
+ public ContentPresenter ViewPort { get; }
+
+ public Vector Offset
+ {
+ get => new(HorizontalScrollBar.Value, VerticalScrollBar.Value);
+ set
+ {
+ HorizontalScrollBar.Value = value.X;
+ VerticalScrollBar.Value = value.Y;
+ RaisePropertyChanged();
+ TriggerRender();
+ }
+ }
+
+ public Size ViewPortSize => ViewPort.Bounds.Size;
+ #endregion
+
+ #region Private Members
+ private Point _startMousePosition;
+ private Vector _startScrollPosition;
+ private bool _isPanning;
+ private bool _isSelecting;
+ private Bitmap? _trackerImage;
+ private bool _canRender = true;
+ private Point _pointerPosition;
+ #endregion
+
+ #region Properties
+ public static readonly DirectProperty CanRenderProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(CanRender),
+ o => o.CanRender);
+
+ ///
+ /// Gets or sets if control can render the image
+ ///
+ public bool CanRender
+ {
+ get => _canRender;
+ set
+ {
+ if (!SetAndRaise(CanRenderProperty, ref _canRender, value)) return;
+ if (_canRender) TriggerRender();
+ }
+ }
+
+ public static readonly StyledProperty GridCellSizeProperty =
+ AvaloniaProperty.Register(nameof(GridCellSize), 15);
+
+ ///
+ /// Gets or sets the grid cell size
+ ///
+ public byte GridCellSize
+ {
+ get => GetValue(GridCellSizeProperty);
+ set => SetValue(GridCellSizeProperty, value);
+ }
+
+ public static readonly StyledProperty GridColorProperty =
+ AvaloniaProperty.Register(nameof(GridColor), Brushes.Gainsboro);
+
+ ///
+ /// Gets or sets the color used to create the checkerboard style background
+ ///
+ public ISolidColorBrush GridColor
+ {
+ get => GetValue(GridColorProperty);
+ set => SetValue(GridColorProperty, value);
+ }
+
+ public static readonly StyledProperty GridColorAlternateProperty =
+ AvaloniaProperty.Register(nameof(GridColorAlternate), Brushes.White);
+
+ ///
+ /// Gets or sets the color used to create the checkerboard style background
+ ///
+ public ISolidColorBrush GridColorAlternate
+ {
+ get => GetValue(GridColorAlternateProperty);
+ set => SetValue(GridColorAlternateProperty, value);
+ }
+
+ public static readonly StyledProperty ImageProperty =
+ AvaloniaProperty.Register(nameof(Image));
+
+ ///
+ /// Gets or sets the image to be displayed
+ ///
+ public Bitmap? Image
+ {
+ get => GetValue(ImageProperty);
+ set
+ {
+ SetValue(ImageProperty, value);
+
+ if (value is null)
+ {
+ SelectNone();
+ }
+
+ UpdateViewPort();
+ TriggerRender();
+
+ RaisePropertyChanged(nameof(IsImageLoaded));
+ }
+ }
+
+ public WriteableBitmap? ImageAsWriteableBitmap
+ {
+ get
+ {
+ if (Image is null) return null;
+ return (WriteableBitmap) Image;
+ }
+ }
+
+ public bool IsImageLoaded => Image is not null;
+
+ public static readonly DirectProperty TrackerImageProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(TrackerImage),
+ o => o.TrackerImage,
+ (o, v) => o.TrackerImage = v);
+
+ ///
+ /// Gets or sets an image to follow the mouse pointer
+ ///
+ public Bitmap? TrackerImage
+ {
+ get => _trackerImage;
+ set
+ {
+ if (!SetAndRaise(TrackerImageProperty, ref _trackerImage, value)) return;
+ TriggerRender();
+ RaisePropertyChanged(nameof(HaveTrackerImage));
+ }
+ }
+
+ public bool HaveTrackerImage => _trackerImage is not null;
+
+ public static readonly StyledProperty TrackerImageAutoZoomProperty =
+ AvaloniaProperty.Register(nameof(TrackerImageAutoZoom), true);
+
+ ///
+ /// Gets or sets if the tracker image will be scaled to the current zoom
+ ///
+ public bool TrackerImageAutoZoom
+ {
+ get => GetValue(TrackerImageAutoZoomProperty);
+ set => SetValue(TrackerImageAutoZoomProperty, value);
+ }
+
+ public bool IsHorizontalBarVisible
+ {
+ get
+ {
+ if (Image is null) return false;
+ if (SizeMode != SizeModes.Normal) return false;
+ return ScaledImageWidth > ViewPortSize.Width;
+ }
+ }
+
+ public bool IsVerticalBarVisible
+ {
+ get
+ {
+ if (Image is null) return false;
+ if (SizeMode != SizeModes.Normal) return false;
+ return ScaledImageHeight > ViewPortSize.Height;
+ }
+ }
+
+ public static readonly StyledProperty ShowGridProperty =
+ AvaloniaProperty.Register(nameof(ShowGrid), true);
+
+ ///
+ /// Gets or sets the grid visibility when reach high zoom levels
+ ///
+ public bool ShowGrid
+ {
+ get => GetValue(ShowGridProperty);
+ set => SetValue(ShowGridProperty, value);
+ }
+
+ public static readonly DirectProperty PointerPositionProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(PointerPosition),
+ o => o.PointerPosition);
+
+ ///
+ /// Gets the current pointer position
+ ///
+ public Point PointerPosition
+ {
+ get => _pointerPosition;
+ private set => SetAndRaise(PointerPositionProperty, ref _pointerPosition, value);
+ }
+
+ public static readonly DirectProperty IsPanningProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(IsPanning),
+ o => o.IsPanning);
+
+ ///
+ /// Gets if control is currently panning
+ ///
+ public bool IsPanning
+ {
+ get => _isPanning;
+ protected set
+ {
+ if (!SetAndRaise(IsPanningProperty, ref _isPanning, value)) return;
+ _startScrollPosition = Offset;
+
+ if (value)
+ {
+ Cursor = new Cursor(StandardCursorType.SizeAll);
+ //this.OnPanStart(EventArgs.Empty);
+ }
+ else
+ {
+ Cursor = Cursor.Default;
+ //this.OnPanEnd(EventArgs.Empty);
+ }
+ }
+ }
+
+ public static readonly DirectProperty IsSelectingProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(IsSelecting),
+ o => o.IsSelecting);
+
+ ///
+ /// Gets if control is currently selecting a ROI
+ ///
+ public bool IsSelecting
+ {
+ get => _isSelecting;
+ protected set => SetAndRaise(IsSelectingProperty, ref _isSelecting, value);
+ }
+
+ ///
+ /// Gets the center point of the viewport
+ ///
+ public Point CenterPoint
+ {
+ get
+ {
+ var viewport = GetImageViewPort();
+ return new(viewport.Width / 2, viewport.Height / 2);
+ }
+ }
+
+ public static readonly StyledProperty AutoPanProperty =
+ AvaloniaProperty.Register(nameof(AutoPan), true);
+
+ ///
+ /// Gets or sets if the control can pan with the mouse
+ ///
+ public bool AutoPan
+ {
+ get => GetValue(AutoPanProperty);
+ set => SetValue(AutoPanProperty, value);
+ }
+
+ public static readonly StyledProperty PanWithMouseButtonsProperty =
+ AvaloniaProperty.Register(nameof(PanWithMouseButtons), MouseButtons.LeftButton | MouseButtons.MiddleButton | MouseButtons.RightButton);
+
+ ///
+ /// Gets or sets the mouse buttons to pan the image
+ ///
+ public MouseButtons PanWithMouseButtons
+ {
+ get => GetValue(PanWithMouseButtonsProperty);
+ set => SetValue(PanWithMouseButtonsProperty, value);
+ }
+
+ public static readonly StyledProperty PanWithArrowsProperty =
+ AvaloniaProperty.Register(nameof(PanWithArrows), true);
+
+ ///
+ /// Gets or sets if the control can pan with the keyboard arrows
+ ///
+ public bool PanWithArrows
+ {
+ get => GetValue(PanWithArrowsProperty);
+ set => SetValue(PanWithArrowsProperty, value);
+ }
+
+ public static readonly StyledProperty SelectWithMouseButtonsProperty =
+ AvaloniaProperty.Register(nameof(SelectWithMouseButtons), MouseButtons.LeftButton | MouseButtons.RightButton);
+
+
+ ///
+ /// Gets or sets the mouse buttons to select a region on image
+ ///
+ public MouseButtons SelectWithMouseButtons
+ {
+ get => GetValue(SelectWithMouseButtonsProperty);
+ set => SetValue(SelectWithMouseButtonsProperty, value);
+ }
+
+ public static readonly StyledProperty InvertMousePanProperty =
+ AvaloniaProperty.Register(nameof(InvertMousePan), false);
+
+ ///
+ /// Gets or sets if mouse pan is inverted
+ ///
+ public bool InvertMousePan
+ {
+ get => GetValue(InvertMousePanProperty);
+ set => SetValue(InvertMousePanProperty, value);
+ }
+
+ public static readonly StyledProperty AutoCenterProperty =
+ AvaloniaProperty.Register(nameof(AutoCenter), true);
+
+ ///
+ /// Gets or sets if image is auto centered
+ ///
+ public bool AutoCenter
+ {
+ get => GetValue(AutoCenterProperty);
+ set => SetValue(AutoCenterProperty, value);
+ }
+
+ public static readonly StyledProperty SizeModeProperty =
+ AvaloniaProperty.Register(nameof(SizeMode), SizeModes.Normal);
+
+ ///
+ /// Gets or sets the image size mode
+ ///
+ public SizeModes SizeMode
+ {
+ get => GetValue(SizeModeProperty);
+ set
+ {
+ SetValue(SizeModeProperty, value);
+ SizeModeChanged();
+ RaisePropertyChanged(nameof(IsHorizontalBarVisible));
+ RaisePropertyChanged(nameof(IsVerticalBarVisible));
+ }
+ }
+
+ private void SizeModeChanged()
+ {
+ switch (SizeMode)
+ {
+ case SizeModes.Normal:
+ HorizontalScrollBar.Visibility = ScrollBarVisibility.Auto;
+ VerticalScrollBar.Visibility = ScrollBarVisibility.Auto;
+ break;
+ case SizeModes.Stretch:
+ case SizeModes.Fit:
+ HorizontalScrollBar.Visibility = ScrollBarVisibility.Hidden;
+ VerticalScrollBar.Visibility = ScrollBarVisibility.Hidden;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(SizeMode), SizeMode, null);
+ }
+ }
+
+ public static readonly StyledProperty AllowZoomProperty =
+ AvaloniaProperty.Register(nameof(AllowZoom), true);
+
+ ///
+ /// Gets or sets if zoom is allowed
+ ///
+ public bool AllowZoom
+ {
+ get => GetValue(AllowZoomProperty);
+ set => SetValue(AllowZoomProperty, value);
+ }
+
+ public static readonly DirectProperty ZoomLevelsProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(ZoomLevels),
+ o => o.ZoomLevels,
+ (o, v) => o.ZoomLevels = v);
+
+ ZoomLevelCollection _zoomLevels = ZoomLevelCollection.Default;
+ ///
+ /// Gets or sets the zoom levels.
+ ///
+ /// The zoom levels.
+ public ZoomLevelCollection ZoomLevels
+ {
+ get => _zoomLevels;
+ set => SetAndRaise(ZoomLevelsProperty, ref _zoomLevels, value);
+ }
+
+ public static readonly StyledProperty MinZoomProperty =
+ AvaloniaProperty.Register(nameof(MinZoom), 10);
+
+ ///
+ /// Gets or sets the minimum possible zoom.
+ ///
+ /// The zoom.
+ public int MinZoom
+ {
+ get => GetValue(MinZoomProperty);
+ set => SetValue(MinZoomProperty, value);
+ }
+
+ public static readonly StyledProperty MaxZoomProperty =
+ AvaloniaProperty.Register(nameof(MaxZoom), 3500);
+
+ ///
+ /// Gets or sets the maximum possible zoom.
+ ///
+ /// The zoom.
+ public int MaxZoom
+ {
+ get => GetValue(MaxZoomProperty);
+ set => SetValue(MaxZoomProperty, value);
+ }
+
+
+ public static readonly DirectProperty OldZoomProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(OldZoom),
+ o => o.OldZoom);
+
+ private int _oldZoom = 100;
+
+ ///
+ /// Gets the previous zoom value
+ ///
+ /// The zoom.
+ public int OldZoom
+ {
+ get => _oldZoom;
+ private set => SetAndRaise(OldZoomProperty, ref _oldZoom, value);
+ }
+
+ public static readonly StyledProperty ZoomProperty =
+ AvaloniaProperty.Register(nameof(Zoom), 100);
+
+ ///
+ /// Gets or sets the zoom.
+ ///
+ /// The zoom.
+ public int Zoom
+ {
+ get => GetValue(ZoomProperty);
+ set
+ {
+ var newZoom = Math.Clamp(value, MinZoom, MaxZoom);
+
+ var previousZoom = Zoom;
+ if (previousZoom == newZoom) return;
+ OldZoom = previousZoom;
+ SetValue(ZoomProperty, value);
+
+ UpdateViewPort();
+ TriggerRender();
+
+ RaisePropertyChanged(nameof(IsHorizontalBarVisible));
+ RaisePropertyChanged(nameof(IsVerticalBarVisible));
+ }
+ }
+
+ public bool IsActualSize => Zoom == 100;
+
+ ///
+ /// Gets the zoom factor, the zoom / 100
+ ///
+ public double ZoomFactor => Zoom / 100.0;
+
+ ///
+ /// Gets the width of the scaled image.
+ ///
+ /// The width of the scaled image.
+ public double ScaledImageWidth => Image?.Size.Width * ZoomFactor ?? 0;
+
+ ///
+ /// Gets the height of the scaled image.
+ ///
+ /// The height of the scaled image.
+ public double ScaledImageHeight => Image?.Size.Height * ZoomFactor ?? 0;
+
+ public static readonly StyledProperty PixelGridColorProperty =
+ AvaloniaProperty.Register(nameof(PixelGridColor), Brushes.DimGray);
+
+ ///
+ /// Gets or sets the color of the pixel grid.
+ ///
+ /// The color of the pixel grid.
+ public ISolidColorBrush PixelGridColor
+ {
+ get => GetValue(PixelGridColorProperty);
+ set => SetValue(PixelGridColorProperty, value);
+ }
+
+ public static readonly StyledProperty PixelGridZoomThresholdProperty =
+ AvaloniaProperty.Register(nameof(PixelGridZoomThreshold), 5);
+
+ ///
+ /// Gets or sets the minimum size of zoomed pixel's before the pixel grid will be drawn
+ ///
+ /// The pixel grid threshold.
+
+ public int PixelGridZoomThreshold
+ {
+ get => GetValue(PixelGridZoomThresholdProperty);
+ set => SetValue(PixelGridZoomThresholdProperty, value);
+ }
+
+ public static readonly StyledProperty SelectionModeProperty =
+ AvaloniaProperty.Register(nameof(SelectionMode), SelectionModes.None);
+
+ public SelectionModes SelectionMode
+ {
+ get => GetValue(SelectionModeProperty);
+ set => SetValue(SelectionModeProperty, value);
+ }
+
+ public static readonly StyledProperty SelectionColorProperty =
+ AvaloniaProperty.Register(nameof(SelectionColor), new SolidColorBrush(new Color(127, 0, 128, 255)));
+
+ public ISolidColorBrush SelectionColor
+ {
+ get => GetValue(SelectionColorProperty);
+ set => SetValue(SelectionColorProperty, value);
+ }
+
+ public static readonly StyledProperty SelectionRegionProperty =
+ AvaloniaProperty.Register(nameof(SelectionRegion), Rect.Empty);
+
+
+ public Rect SelectionRegion
+ {
+ get => GetValue(SelectionRegionProperty);
+ set
+ {
+ SetValue(SelectionRegionProperty, value);
+ //if (!RaiseAndSetIfChanged(ref _selectionRegion, value)) return;
+ TriggerRender();
+ RaisePropertyChanged(nameof(HaveSelection));
+ RaisePropertyChanged(nameof(SelectionRegionNet));
+ RaisePropertyChanged(nameof(SelectionPixelSize));
+ }
+ }
+
+ public Rectangle SelectionRegionNet
+ {
+ get
+ {
+ var rect = SelectionRegion;
+ return new Rectangle((int) Math.Ceiling(rect.X), (int)Math.Ceiling(rect.Y),
+ (int)rect.Width, (int)rect.Height);
+ }
+ }
+
+ public PixelSize SelectionPixelSize
+ {
+ get
+ {
+ var rect = SelectionRegion;
+ return new PixelSize((int) rect.Width, (int) rect.Height);
+ }
+ }
+
+ public bool HaveSelection => !SelectionRegion.IsEmpty;
+ #endregion
+
+ #region Constructor
+ public AdvancedImageBox()
+ {
+ InitializeComponent();
+
+ FocusableProperty.OverrideDefaultValue(typeof(AdvancedImageBox), true);
+ AffectsRender(ShowGridProperty);
+
+ HorizontalScrollBar = this.FindControl("HorizontalScrollBar");
+ VerticalScrollBar = this.FindControl("VerticalScrollBar");
+ ViewPort = this.FindControl("ViewPort");
+
+ SizeModeChanged();
+
+ HorizontalScrollBar.Scroll += ScrollBarOnScroll;
+ VerticalScrollBar.Scroll += ScrollBarOnScroll;
+ ViewPort.PointerWheelChanged += FillContainerOnPointerWheelChanged;
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ #endregion
+
+ #region Render methods
+ public void TriggerRender(bool renderOnlyCursorTracker = false)
+ {
+ if (!_canRender) return;
+ if (renderOnlyCursorTracker && _trackerImage is null) return;
+
+ InvalidateVisual();
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ //Debug.WriteLine($"Render: {DateTime.Now.Ticks}");
+ base.Render(context);
+
+ // Draw Grid
+ var gridCellSize = GridCellSize;
+ if (ShowGrid & gridCellSize > 0 && (!IsHorizontalBarVisible || !IsVerticalBarVisible))
+ {
+ // draw the background
+ var gridColor = GridColor;
+ var altColor = GridColorAlternate;
+ var currentColor = gridColor;
+ for (int y = 0; y < ViewPortSize.Height; y += gridCellSize)
+ {
+ var firstRowColor = currentColor;
+
+ for (int x = 0; x < ViewPortSize.Width; x += gridCellSize)
+ {
+ context.FillRectangle(currentColor, new Rect(x, y, gridCellSize, gridCellSize));
+ currentColor = ReferenceEquals(currentColor, gridColor) ? altColor : gridColor;
+ }
+
+ if (Equals(firstRowColor, currentColor))
+ currentColor = ReferenceEquals(currentColor, gridColor) ? altColor : gridColor;
+ }
+
+ }
+ /*else
+ {
+ context.FillRectangle(Background, new Rect(0, 0, Viewport.Width, Viewport.Height));
+ }*/
+
+ var image = Image;
+ if (image is null) return;
+ // Draw iamge
+ context.DrawImage(image,
+ GetSourceImageRegion(),
+ GetImageViewPort()
+ );
+
+ var zoomFactor = ZoomFactor;
+
+
+ if (HaveTrackerImage && _pointerPosition.X >= 0 && _pointerPosition.Y >= 0)
+ {
+ var destSize = TrackerImageAutoZoom
+ ? new Size(_trackerImage!.Size.Width * zoomFactor, _trackerImage.Size.Height * zoomFactor)
+ : image.Size;
+
+ var destPos = new Point(
+ _pointerPosition.X - destSize.Width / 2,
+ _pointerPosition.Y - destSize.Height / 2
+ );
+ context.DrawImage(_trackerImage,
+ new Rect(destPos, destSize)
+ );
+ }
+
+ //SkiaContext.SkCanvas.dr
+ // Draw pixel grid
+ if (zoomFactor > PixelGridZoomThreshold && SizeMode == SizeModes.Normal)
+ {
+ var viewport = GetImageViewPort();
+ var offsetX = Offset.X % zoomFactor;
+ var offsetY = Offset.Y % zoomFactor;
+
+ Pen pen = new(PixelGridColor);
+ for (double x = viewport.X + zoomFactor - offsetX; x < viewport.Right; x += zoomFactor)
+ {
+ context.DrawLine(pen, new Point(x, viewport.X), new Point(x, viewport.Bottom));
+ }
+
+ for (double y = viewport.Y + zoomFactor - offsetY; y < viewport.Bottom; y += zoomFactor)
+ {
+ context.DrawLine(pen, new Point(viewport.Y, y), new Point(viewport.Right, y));
+ }
+
+ context.DrawRectangle(pen, viewport);
+ }
+
+ if (!SelectionRegion.IsEmpty)
+ {
+ var rect = GetOffsetRectangle(SelectionRegion);
+ var selectionColor = SelectionColor;
+ context.FillRectangle(selectionColor, rect);
+ Color color = Color.FromArgb(255, selectionColor.Color.R, selectionColor.Color.G, selectionColor.Color.B);
+ context.DrawRectangle(new Pen(color.ToUint32()), rect);
+ }
+ }
+
+ private bool UpdateViewPort()
+ {
+ if (Image is null)
+ {
+ HorizontalScrollBar.Maximum = 0;
+ VerticalScrollBar.Maximum = 0;
return true;
}
+ var scaledImageWidth = ScaledImageWidth;
+ var scaledImageHeight = ScaledImageHeight;
+ var width = scaledImageWidth - HorizontalScrollBar.ViewportSize;
+ var height = scaledImageHeight - VerticalScrollBar.ViewportSize;
+ //var width = scaledImageWidth <= Viewport.Width ? Viewport.Width : scaledImageWidth;
+ //var height = scaledImageHeight <= Viewport.Height ? Viewport.Height : scaledImageHeight;
- protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
+ bool changed = false;
+ if (Math.Abs(HorizontalScrollBar.Maximum - width) > 0.01)
{
+ HorizontalScrollBar.Maximum = width;
+ changed = true;
}
- ///
- /// Notifies listeners that a property value has changed.
- ///
- ///
- /// Name of the property used to notify listeners. This
- /// value is optional and can be provided automatically when invoked from compilers
- /// that support .
- ///
- protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+ if (Math.Abs(VerticalScrollBar.Maximum - scaledImageHeight) > 0.01)
{
- var e = new PropertyChangedEventArgs(propertyName);
- OnPropertyChanged(e);
- _propertyChanged?.Invoke(this, e);
+ VerticalScrollBar.Maximum = height;
+ changed = true;
}
- #endregion
- #region Sub Classes
-
- ///
- /// Represents available levels of zoom in an control
- ///
- public class ZoomLevelCollection : IList
+ /*if (changed)
{
- #region Public Constructors
-
- ///
- /// Initializes a new instance of the class.
- ///
- public ZoomLevelCollection()
+ var newContainer = new ContentControl
{
- List = new SortedList();
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The default values to populate the collection with.
- /// Thrown if the collection parameter is null
- public ZoomLevelCollection(IEnumerable collection)
- : this()
- {
- if (collection == null)
- {
- throw new ArgumentNullException(nameof(collection));
- }
-
- AddRange(collection);
- }
-
- #endregion
-
- #region Public Class Properties
-
- ///
- /// Returns the default zoom levels
- ///
- public static ZoomLevelCollection Default =>
- new(new[] {
- 7, 10, 15, 20, 25, 30, 50, 70, 100, 150, 200, 300, 400, 500, 600, 700, 800, 1200, 1600, 3200
- });
-
- #endregion
-
- #region Public Properties
-
- ///
- /// Gets the number of elements contained in the .
- ///
- ///
- /// The number of elements contained in the .
- ///
- public int Count => List.Count;
-
- ///
- /// Gets a value indicating whether the is read-only.
- ///
- /// true if this instance is read only; otherwise, false.
- /// true if the is read-only; otherwise, false.
- ///
- public bool IsReadOnly => false;
-
- ///
- /// Gets or sets the zoom level at the specified index.
- ///
- /// The index.
- public int this[int index]
- {
- get => List.Values[index];
- set
- {
- List.RemoveAt(index);
- Add(value);
- }
- }
-
- #endregion
-
- #region Protected Properties
-
- ///
- /// Gets or sets the backing list.
- ///
- protected SortedList List { get; set; }
-
- #endregion
-
- #region Public Members
-
- ///
- /// Adds an item to the .
- ///
- /// The object to add to the .
- public void Add(int item)
- {
- List.Add(item, item);
- }
-
- ///
- /// Adds a range of items to the .
- ///
- /// The items to add to the collection.
- /// Thrown if the collection parameter is null.
- public void AddRange(IEnumerable collection)
- {
- if (collection == null)
- {
- throw new ArgumentNullException(nameof(collection));
- }
-
- foreach (int value in collection)
- {
- Add(value);
- }
- }
-
- ///
- /// Removes all items from the .
- ///
- public void Clear()
- {
- List.Clear();
- }
-
- ///
- /// Determines whether the contains a specific value.
- ///
- /// The object to locate in the .
- /// true if is found in the ; otherwise, false.
- public bool Contains(int item)
- {
- return List.ContainsKey(item);
- }
-
- ///
- /// Copies a range of elements this collection into a destination .
- ///
- /// The that receives the data.
- /// A 64-bit integer that represents the index in the at which storing begins.
- public void CopyTo(int[] array, int arrayIndex)
- {
- for (int i = 0; i < Count; i++)
- {
- array[arrayIndex + i] = List.Values[i];
- }
- }
-
- ///
- /// Finds the index of a zoom level matching or nearest to the specified value.
- ///
- /// The zoom level.
- public int FindNearest(int zoomLevel)
- {
- int nearestValue = List.Values[0];
- int nearestDifference = Math.Abs(nearestValue - zoomLevel);
- for (int i = 1; i < Count; i++)
- {
- int value = List.Values[i];
- int difference = Math.Abs(value - zoomLevel);
- if (difference < nearestDifference)
- {
- nearestValue = value;
- nearestDifference = difference;
- }
- }
- return nearestValue;
- }
-
- ///
- /// Returns an enumerator that iterates through the collection.
- ///
- /// A that can be used to iterate through the collection.
- public IEnumerator GetEnumerator()
- {
- return List.Values.GetEnumerator();
- }
-
- ///
- /// Determines the index of a specific item in the .
- ///
- /// The object to locate in the .
- /// The index of if found in the list; otherwise, -1.
- public int IndexOf(int item)
- {
- return List.IndexOfKey(item);
- }
-
- ///
- /// Not implemented.
- ///
- /// The index.
- /// The item.
- /// Not implemented
- public void Insert(int index, int item)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Returns the next increased zoom level for the given current zoom.
- ///
- /// The current zoom level.
- /// The next matching increased zoom level for the given current zoom if applicable, otherwise the nearest zoom.
- public int NextZoom(int zoomLevel)
- {
- var index = IndexOf(FindNearest(zoomLevel));
- if (index < Count - 1)
- {
- index++;
- }
-
- return this[index];
- }
-
- ///
- /// Returns the next decreased zoom level for the given current zoom.
- ///
- /// The current zoom level.
- /// The next matching decreased zoom level for the given current zoom if applicable, otherwise the nearest zoom.
- public int PreviousZoom(int zoomLevel)
- {
- var index = IndexOf(FindNearest(zoomLevel));
- if (index > 0)
- {
- index--;
- }
-
- return this[index];
- }
-
- ///
- /// Removes the first occurrence of a specific object from the .
- ///
- /// The object to remove from the .
- /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original .
- public bool Remove(int item)
- {
- return List.Remove(item);
- }
-
- ///
- /// Removes the element at the specified index of the .
- ///
- /// The zero-based index of the element to remove.
- public void RemoveAt(int index)
- {
- List.RemoveAt(index);
- }
-
- ///
- /// Copies the elements of the to a new array.
- ///
- /// An array containing copies of the elements of the .
- public int[] ToArray()
- {
- int[] results;
-
- results = new int[Count];
- CopyTo(results, 0);
-
- return results;
- }
-
- #endregion
-
- #region IList Members
-
- ///
- /// Returns an enumerator that iterates through a collection.
- ///
- /// An object that can be used to iterate through the collection.
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- #endregion
- }
-
- #endregion
-
- #region Enums
-
- ///
- /// Determines the sizing mode of an image hosted in an control.
- ///
- public enum SizeModes : byte
- {
- ///
- /// The image is displayed according to current zoom and scroll properties.
- ///
- Normal,
-
- ///
- /// The image is stretched to fill the client area of the control.
- ///
- Stretch,
-
- ///
- /// The image is stretched to fill as much of the client area of the control as possible, whilst retaining the same aspect ratio for the width and height.
- ///
- Fit
- }
-
- [Flags]
- public enum MouseButtons : byte
- {
- None = 0,
- LeftButton = 1,
- MiddleButton = 2,
- RightButton = 4
- }
-
- ///
- /// Describes the zoom action occuring
- ///
- [Flags]
- public enum ZoomActions : byte
- {
- ///
- /// No action.
- ///
- None = 0,
-
- ///
- /// The control is increasing the zoom.
- ///
- ZoomIn = 1,
-
- ///
- /// The control is decreasing the zoom.
- ///
- ZoomOut = 2,
-
- ///
- /// The control zoom was reset.
- ///
- ActualSize = 4
- }
-
- public enum SelectionModes
- {
- ///
- /// No selection.
- ///
- None,
-
- ///
- /// Rectangle selection.
- ///
- Rectangle,
-
- ///
- /// Zoom selection.
- ///
- Zoom
- }
-
- #endregion
-
- #region UI Controls
- public ScrollBar HorizontalScrollBar { get; }
- public ScrollBar VerticalScrollBar { get; }
- public ContentPresenter ViewPort { get; }
-
- public Vector Offset
- {
- get => new(HorizontalScrollBar.Value, VerticalScrollBar.Value);
- set
- {
- HorizontalScrollBar.Value = value.X;
- VerticalScrollBar.Value = value.Y;
- RaisePropertyChanged();
- TriggerRender();
- }
- }
-
- public Size ViewPortSize => ViewPort.Bounds.Size;
- #endregion
-
- #region Private Members
- private Point _startMousePosition;
- private Vector _startScrollPosition;
- private bool _isPanning;
- private bool _isSelecting;
- private Bitmap _trackerImage;
- private bool _canRender = true;
- private Point _pointerPosition;
- #endregion
-
- #region Properties
- public static readonly DirectProperty CanRenderProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(CanRender),
- o => o.CanRender);
-
- ///
- /// Gets or sets if control can render the image
- ///
- public bool CanRender
- {
- get => _canRender;
- set
- {
- if (!SetAndRaise(CanRenderProperty, ref _canRender, value)) return;
- if (_canRender) TriggerRender();
- }
- }
-
- public static readonly StyledProperty GridCellSizeProperty =
- AvaloniaProperty.Register(nameof(GridCellSize), 15);
-
- ///
- /// Gets or sets the grid cell size
- ///
- public byte GridCellSize
- {
- get => GetValue(GridCellSizeProperty);
- set => SetValue(GridCellSizeProperty, value);
- }
-
- public static readonly StyledProperty GridColorProperty =
- AvaloniaProperty.Register(nameof(GridColor), Brushes.Gainsboro);
-
- ///
- /// Gets or sets the color used to create the checkerboard style background
- ///
- public ISolidColorBrush GridColor
- {
- get => GetValue(GridColorProperty);
- set => SetValue(GridColorProperty, value);
- }
-
- public static readonly StyledProperty GridColorAlternateProperty =
- AvaloniaProperty.Register(nameof(GridColorAlternate), Brushes.White);
-
- ///
- /// Gets or sets the color used to create the checkerboard style background
- ///
- public ISolidColorBrush GridColorAlternate
- {
- get => GetValue(GridColorAlternateProperty);
- set => SetValue(GridColorAlternateProperty, value);
- }
-
- public static readonly StyledProperty ImageProperty =
- AvaloniaProperty.Register(nameof(Image));
-
- ///
- /// Gets or sets the image to be displayed
- ///
- public Bitmap Image
- {
- get => GetValue(ImageProperty);
- set
- {
- SetValue(ImageProperty, value);
-
- if (value is null)
- {
- SelectNone();
- }
-
- UpdateViewPort();
- TriggerRender();
-
- RaisePropertyChanged(nameof(IsImageLoaded));
- }
- }
-
- public WriteableBitmap ImageAsWriteableBitmap => (WriteableBitmap) Image;
-
- public bool IsImageLoaded => Image is not null;
-
- public static readonly DirectProperty TrackerImageProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(TrackerImage),
- o => o.TrackerImage,
- (o, v) => o.TrackerImage = v);
-
- ///
- /// Gets or sets an image to follow the mouse pointer
- ///
- public Bitmap TrackerImage
- {
- get => _trackerImage;
- set
- {
- if (!SetAndRaise(TrackerImageProperty, ref _trackerImage, value)) return;
- TriggerRender();
- RaisePropertyChanged(nameof(HaveTrackerImage));
- }
- }
-
- public bool HaveTrackerImage => _trackerImage is not null;
-
- public static readonly StyledProperty TrackerImageAutoZoomProperty =
- AvaloniaProperty.Register(nameof(TrackerImageAutoZoom), true);
-
- ///
- /// Gets or sets if the tracker image will be scaled to the current zoom
- ///
- public bool TrackerImageAutoZoom
- {
- get => GetValue(TrackerImageAutoZoomProperty);
- set => SetValue(TrackerImageAutoZoomProperty, value);
- }
-
- public bool IsHorizontalBarVisible
- {
- get
- {
- if (Image is null) return false;
- if (SizeMode != SizeModes.Normal) return false;
- return ScaledImageWidth > ViewPortSize.Width;
- }
- }
-
- public bool IsVerticalBarVisible
- {
- get
- {
- if (Image is null) return false;
- if (SizeMode != SizeModes.Normal) return false;
- return ScaledImageHeight > ViewPortSize.Height;
- }
- }
-
- public static readonly StyledProperty ShowGridProperty =
- AvaloniaProperty.Register(nameof(ShowGrid), true);
-
- ///
- /// Gets or sets the grid visibility when reach high zoom levels
- ///
- public bool ShowGrid
- {
- get => GetValue(ShowGridProperty);
- set => SetValue(ShowGridProperty, value);
- }
-
- public static readonly DirectProperty PointerPositionProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(PointerPosition),
- o => o.PointerPosition);
-
- ///
- /// Gets the current pointer position
- ///
- public Point PointerPosition
- {
- get => _pointerPosition;
- private set => SetAndRaise(PointerPositionProperty, ref _pointerPosition, value);
- }
-
- public static readonly DirectProperty IsPanningProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(IsPanning),
- o => o.IsPanning);
-
- ///
- /// Gets if control is currently panning
- ///
- public bool IsPanning
- {
- get => _isPanning;
- protected set
- {
- if (!SetAndRaise(IsPanningProperty, ref _isPanning, value)) return;
- _startScrollPosition = Offset;
-
- if (value)
- {
- Cursor = new Cursor(StandardCursorType.SizeAll);
- //this.OnPanStart(EventArgs.Empty);
- }
- else
- {
- Cursor = Cursor.Default;
- //this.OnPanEnd(EventArgs.Empty);
- }
- }
- }
-
- public static readonly DirectProperty IsSelectingProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(IsSelecting),
- o => o.IsSelecting);
-
- ///
- /// Gets if control is currently selecting a ROI
- ///
- public bool IsSelecting
- {
- get => _isSelecting;
- protected set => SetAndRaise(IsSelectingProperty, ref _isSelecting, value);
- }
-
- ///
- /// Gets the center point of the viewport
- ///
- public Point CenterPoint
- {
- get
- {
- var viewport = GetImageViewPort();
- return new(viewport.Width / 2, viewport.Height / 2);
- }
- }
-
- public static readonly StyledProperty AutoPanProperty =
- AvaloniaProperty.Register(nameof(AutoPan), true);
-
- ///
- /// Gets or sets if the control can pan with the mouse
- ///
- public bool AutoPan
- {
- get => GetValue(AutoPanProperty);
- set => SetValue(AutoPanProperty, value);
- }
-
- public static readonly StyledProperty PanWithMouseButtonsProperty =
- AvaloniaProperty.Register(nameof(PanWithMouseButtons), MouseButtons.LeftButton | MouseButtons.MiddleButton | MouseButtons.RightButton);
-
- ///
- /// Gets or sets the mouse buttons to pan the image
- ///
- public MouseButtons PanWithMouseButtons
- {
- get => GetValue(PanWithMouseButtonsProperty);
- set => SetValue(PanWithMouseButtonsProperty, value);
- }
-
- public static readonly StyledProperty PanWithArrowsProperty =
- AvaloniaProperty.Register(nameof(PanWithArrows), true);
-
- ///
- /// Gets or sets if the control can pan with the keyboard arrows
- ///
- public bool PanWithArrows
- {
- get => GetValue(PanWithArrowsProperty);
- set => SetValue(PanWithArrowsProperty, value);
- }
-
- public static readonly StyledProperty SelectWithMouseButtonsProperty =
- AvaloniaProperty.Register(nameof(SelectWithMouseButtons), MouseButtons.LeftButton | MouseButtons.RightButton);
-
-
- ///
- /// Gets or sets the mouse buttons to select a region on image
- ///
- public MouseButtons SelectWithMouseButtons
- {
- get => GetValue(SelectWithMouseButtonsProperty);
- set => SetValue(SelectWithMouseButtonsProperty, value);
- }
-
- public static readonly StyledProperty InvertMousePanProperty =
- AvaloniaProperty.Register(nameof(InvertMousePan), false);
-
- ///
- /// Gets or sets if mouse pan is inverted
- ///
- public bool InvertMousePan
- {
- get => GetValue(InvertMousePanProperty);
- set => SetValue(InvertMousePanProperty, value);
- }
-
- public static readonly StyledProperty AutoCenterProperty =
- AvaloniaProperty.Register(nameof(AutoCenter), true);
-
- ///
- /// Gets or sets if image is auto centered
- ///
- public bool AutoCenter
- {
- get => GetValue(AutoCenterProperty);
- set => SetValue(AutoCenterProperty, value);
- }
-
- public static readonly StyledProperty SizeModeProperty =
- AvaloniaProperty.Register(nameof(SizeMode), SizeModes.Normal);
-
- ///
- /// Gets or sets the image size mode
- ///
- public SizeModes SizeMode
- {
- get => GetValue(SizeModeProperty);
- set
- {
- SetValue(SizeModeProperty, value);
- SizeModeChanged();
- RaisePropertyChanged(nameof(IsHorizontalBarVisible));
- RaisePropertyChanged(nameof(IsVerticalBarVisible));
- }
- }
-
- private void SizeModeChanged()
- {
- switch (SizeMode)
- {
- case SizeModes.Normal:
- HorizontalScrollBar.Visibility = ScrollBarVisibility.Auto;
- VerticalScrollBar.Visibility = ScrollBarVisibility.Auto;
- break;
- case SizeModes.Stretch:
- case SizeModes.Fit:
- HorizontalScrollBar.Visibility = ScrollBarVisibility.Hidden;
- VerticalScrollBar.Visibility = ScrollBarVisibility.Hidden;
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(SizeMode), SizeMode, null);
- }
- }
-
- public static readonly StyledProperty AllowZoomProperty =
- AvaloniaProperty.Register(nameof(AllowZoom), true);
-
- ///
- /// Gets or sets if zoom is allowed
- ///
- public bool AllowZoom
- {
- get => GetValue(AllowZoomProperty);
- set => SetValue(AllowZoomProperty, value);
- }
-
- public static readonly DirectProperty ZoomLevelsProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(ZoomLevels),
- o => o.ZoomLevels,
- (o, v) => o.ZoomLevels = v);
-
- ZoomLevelCollection _zoomLevels = ZoomLevelCollection.Default;
- ///
- /// Gets or sets the zoom levels.
- ///
- /// The zoom levels.
- public ZoomLevelCollection ZoomLevels
- {
- get => _zoomLevels;
- set => SetAndRaise(ZoomLevelsProperty, ref _zoomLevels, value);
- }
-
- public static readonly StyledProperty MinZoomProperty =
- AvaloniaProperty.Register(nameof(MinZoom), 10);
-
- ///
- /// Gets or sets the minimum possible zoom.
- ///
- /// The zoom.
- public int MinZoom
- {
- get => GetValue(MinZoomProperty);
- set => SetValue(MinZoomProperty, value);
- }
-
- public static readonly StyledProperty MaxZoomProperty =
- AvaloniaProperty.Register(nameof(MaxZoom), 3500);
-
- ///
- /// Gets or sets the maximum possible zoom.
- ///
- /// The zoom.
- public int MaxZoom
- {
- get => GetValue(MaxZoomProperty);
- set => SetValue(MaxZoomProperty, value);
- }
-
-
- public static readonly DirectProperty OldZoomProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(OldZoom),
- o => o.OldZoom);
-
- private int _oldZoom = 100;
-
- ///
- /// Gets the previous zoom value
- ///
- /// The zoom.
- public int OldZoom
- {
- get => _oldZoom;
- private set => SetAndRaise(OldZoomProperty, ref _oldZoom, value);
- }
-
- public static readonly StyledProperty ZoomProperty =
- AvaloniaProperty.Register(nameof(Zoom), 100);
-
- ///
- /// Gets or sets the zoom.
- ///
- /// The zoom.
- public int Zoom
- {
- get => GetValue(ZoomProperty);
- set
- {
- var newZoom = Math.Clamp(value, MinZoom, MaxZoom);
-
- var previousZoom = Zoom;
- if (previousZoom == newZoom) return;
- OldZoom = previousZoom;
- SetValue(ZoomProperty, value);
-
- UpdateViewPort();
- TriggerRender();
-
- RaisePropertyChanged(nameof(IsHorizontalBarVisible));
- RaisePropertyChanged(nameof(IsVerticalBarVisible));
- }
- }
-
- public bool IsActualSize => Zoom == 100;
-
- ///
- /// Gets the zoom factor, the zoom / 100
- ///
- public double ZoomFactor => Zoom / 100.0;
-
- ///
- /// Gets the width of the scaled image.
- ///
- /// The width of the scaled image.
- public double ScaledImageWidth => Image.Size.Width * ZoomFactor;
-
- ///
- /// Gets the height of the scaled image.
- ///
- /// The height of the scaled image.
- public double ScaledImageHeight => Image.Size.Height * ZoomFactor;
-
- public static readonly StyledProperty PixelGridColorProperty =
- AvaloniaProperty.Register(nameof(PixelGridColor), Brushes.DimGray);
-
- ///
- /// Gets or sets the color of the pixel grid.
- ///
- /// The color of the pixel grid.
- public ISolidColorBrush PixelGridColor
- {
- get => GetValue(PixelGridColorProperty);
- set => SetValue(PixelGridColorProperty, value);
- }
-
- public static readonly StyledProperty PixelGridZoomThresholdProperty =
- AvaloniaProperty.Register(nameof(PixelGridZoomThreshold), 5);
-
- ///
- /// Gets or sets the minimum size of zoomed pixel's before the pixel grid will be drawn
- ///
- /// The pixel grid threshold.
-
- public int PixelGridZoomThreshold
- {
- get => GetValue(PixelGridZoomThresholdProperty);
- set => SetValue(PixelGridZoomThresholdProperty, value);
- }
-
- public static readonly StyledProperty SelectionModeProperty =
- AvaloniaProperty.Register(nameof(SelectionMode), SelectionModes.None);
-
- public SelectionModes SelectionMode
- {
- get => GetValue(SelectionModeProperty);
- set => SetValue(SelectionModeProperty, value);
- }
-
- public static readonly StyledProperty SelectionColorProperty =
- AvaloniaProperty.Register(nameof(SelectionColor), new SolidColorBrush(new Color(127, 0, 128, 255)));
-
- public ISolidColorBrush SelectionColor
- {
- get => GetValue(SelectionColorProperty);
- set => SetValue(SelectionColorProperty, value);
- }
-
- public static readonly StyledProperty SelectionRegionProperty =
- AvaloniaProperty.Register(nameof(SelectionRegion), Rect.Empty);
-
-
- public Rect SelectionRegion
- {
- get => GetValue(SelectionRegionProperty);
- set
- {
- SetValue(SelectionRegionProperty, value);
- //if (!RaiseAndSetIfChanged(ref _selectionRegion, value)) return;
- TriggerRender();
- RaisePropertyChanged(nameof(HaveSelection));
- RaisePropertyChanged(nameof(SelectionRegionNet));
- RaisePropertyChanged(nameof(SelectionPixelSize));
- }
- }
-
- public Rectangle SelectionRegionNet
- {
- get
- {
- var rect = SelectionRegion;
- return new Rectangle((int) Math.Ceiling(rect.X), (int)Math.Ceiling(rect.Y),
- (int)rect.Width, (int)rect.Height);
- }
- }
-
- public PixelSize SelectionPixelSize
- {
- get
- {
- var rect = SelectionRegion;
- return new PixelSize((int) rect.Width, (int) rect.Height);
- }
- }
-
- public bool HaveSelection => !SelectionRegion.IsEmpty;
- #endregion
-
- #region Constructor
- public AdvancedImageBox()
- {
- InitializeComponent();
-
- FocusableProperty.OverrideDefaultValue(typeof(AdvancedImageBox), true);
- AffectsRender(ShowGridProperty);
-
- HorizontalScrollBar = this.FindControl("HorizontalScrollBar");
- VerticalScrollBar = this.FindControl("VerticalScrollBar");
- ViewPort = this.FindControl("ViewPort");
-
- SizeModeChanged();
-
- HorizontalScrollBar.Scroll += ScrollBarOnScroll;
- VerticalScrollBar.Scroll += ScrollBarOnScroll;
- ViewPort.PointerWheelChanged += FillContainerOnPointerWheelChanged;
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
- #endregion
-
- #region Render methods
- public void TriggerRender(bool renderOnlyCursorTracker = false)
- {
- if (!_canRender) return;
- if (renderOnlyCursorTracker && _trackerImage is null) return;
-
- InvalidateVisual();
- }
-
- public override void Render(DrawingContext context)
- {
- //Debug.WriteLine($"Render: {DateTime.Now.Ticks}");
- base.Render(context);
-
- // Draw Grid
- var gridCellSize = GridCellSize;
- if (ShowGrid & gridCellSize > 0 && (!IsHorizontalBarVisible || !IsVerticalBarVisible))
- {
- // draw the background
- var gridColor = GridColor;
- var altColor = GridColorAlternate;
- var currentColor = gridColor;
- for (int y = 0; y < ViewPortSize.Height; y += gridCellSize)
- {
- var firstRowColor = currentColor;
-
- for (int x = 0; x < ViewPortSize.Width; x += gridCellSize)
- {
- context.FillRectangle(currentColor, new Rect(x, y, gridCellSize, gridCellSize));
- currentColor = ReferenceEquals(currentColor, gridColor) ? altColor : gridColor;
- }
-
- if (Equals(firstRowColor, currentColor))
- currentColor = ReferenceEquals(currentColor, gridColor) ? altColor : gridColor;
- }
-
- }
- /*else
- {
- context.FillRectangle(Background, new Rect(0, 0, Viewport.Width, Viewport.Height));
- }*/
-
- var image = Image;
- if (image is null) return;
- // Draw iamge
- context.DrawImage(image,
- GetSourceImageRegion(),
- GetImageViewPort()
- );
-
- var zoomFactor = ZoomFactor;
-
-
- if (HaveTrackerImage && _pointerPosition.X >= 0 && _pointerPosition.Y >= 0)
- {
- var destSize = TrackerImageAutoZoom
- ? new Size(_trackerImage.Size.Width * zoomFactor, _trackerImage.Size.Height * zoomFactor)
- : image.Size;
-
- var destPos = new Point(
- _pointerPosition.X - destSize.Width / 2,
- _pointerPosition.Y - destSize.Height / 2
- );
- context.DrawImage(_trackerImage,
- new Rect(destPos, destSize)
- );
- }
-
- //SkiaContext.SkCanvas.dr
- // Draw pixel grid
- if (zoomFactor > PixelGridZoomThreshold && SizeMode == SizeModes.Normal)
- {
- var viewport = GetImageViewPort();
- var offsetX = Offset.X % zoomFactor;
- var offsetY = Offset.Y % zoomFactor;
-
- Pen pen = new(PixelGridColor);
- for (double x = viewport.X + zoomFactor - offsetX; x < viewport.Right; x += zoomFactor)
- {
- context.DrawLine(pen, new Point(x, viewport.X), new Point(x, viewport.Bottom));
- }
-
- for (double y = viewport.Y + zoomFactor - offsetY; y < viewport.Bottom; y += zoomFactor)
- {
- context.DrawLine(pen, new Point(viewport.Y, y), new Point(viewport.Right, y));
- }
-
- context.DrawRectangle(pen, viewport);
- }
-
- if (!SelectionRegion.IsEmpty)
- {
- var rect = GetOffsetRectangle(SelectionRegion);
- var selectionColor = SelectionColor;
- context.FillRectangle(selectionColor, rect);
- Color color = Color.FromArgb(255, selectionColor.Color.R, selectionColor.Color.G, selectionColor.Color.B);
- context.DrawRectangle(new Pen(color.ToUint32()), rect);
- }
- }
-
- private bool UpdateViewPort()
- {
- if (Image is null)
- {
- HorizontalScrollBar.Maximum = 0;
- VerticalScrollBar.Maximum = 0;
- return true;
- }
-
- var scaledImageWidth = ScaledImageWidth;
- var scaledImageHeight = ScaledImageHeight;
- var width = scaledImageWidth - HorizontalScrollBar.ViewportSize;
- var height = scaledImageHeight - VerticalScrollBar.ViewportSize;
- //var width = scaledImageWidth <= Viewport.Width ? Viewport.Width : scaledImageWidth;
- //var height = scaledImageHeight <= Viewport.Height ? Viewport.Height : scaledImageHeight;
-
- bool changed = false;
- if (Math.Abs(HorizontalScrollBar.Maximum - width) > 0.01)
- {
- HorizontalScrollBar.Maximum = width;
- changed = true;
- }
-
- if (Math.Abs(VerticalScrollBar.Maximum - scaledImageHeight) > 0.01)
- {
- VerticalScrollBar.Maximum = height;
- changed = true;
- }
-
- /*if (changed)
- {
- var newContainer = new ContentControl
- {
- Width = width,
- Height = height
- };
- FillContainer.Content = SizedContainer = newContainer;
- Debug.WriteLine($"Updated ViewPort: {DateTime.Now.Ticks}");
- //TriggerRender();
- }*/
-
- return changed;
- }
- #endregion
-
- #region Events and Overrides
-
- private void ScrollBarOnScroll(object? sender, ScrollEventArgs e)
- {
- TriggerRender();
- }
-
- /*protected override void OnScrollChanged(ScrollChangedEventArgs e)
- {
- Debug.WriteLine($"ViewportDelta: {e.ViewportDelta} | OffsetDelta: {e.OffsetDelta} | ExtentDelta: {e.ExtentDelta}");
- if (!e.ViewportDelta.IsDefault)
- {
- UpdateViewPort();
- }
-
- TriggerRender();
-
- base.OnScrollChanged(e);
+ Width = width,
+ Height = height
+ };
+ FillContainer.Content = SizedContainer = newContainer;
+ Debug.WriteLine($"Updated ViewPort: {DateTime.Now.Ticks}");
+ //TriggerRender();
}*/
- private void FillContainerOnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
+ return changed;
+ }
+ #endregion
+
+ #region Events and Overrides
+
+ private void ScrollBarOnScroll(object? sender, ScrollEventArgs e)
+ {
+ TriggerRender();
+ }
+
+ /*protected override void OnScrollChanged(ScrollChangedEventArgs e)
+ {
+ Debug.WriteLine($"ViewportDelta: {e.ViewportDelta} | OffsetDelta: {e.OffsetDelta} | ExtentDelta: {e.ExtentDelta}");
+ if (!e.ViewportDelta.IsDefault)
{
- e.Handled = true;
- if (Image is null) return;
- if (AllowZoom && SizeMode == SizeModes.Normal)
- {
- // The MouseWheel event can contain multiple "spins" of the wheel so we need to adjust accordingly
- //double spins = Math.Abs(e.Delta.Y);
- //Debug.WriteLine(e.GetPosition(this));
- // TODO: Really should update the source method to handle multiple increments rather than calling it multiple times
- /*for (int i = 0; i < spins; i++)
- {*/
- ProcessMouseZoom(e.Delta.Y > 0, e.GetPosition(ViewPort));
- //}
- }
+ UpdateViewPort();
}
- protected override void OnPointerPressed(PointerPressedEventArgs e)
+ TriggerRender();
+
+ base.OnScrollChanged(e);
+ }*/
+
+ private void FillContainerOnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
+ {
+ e.Handled = true;
+ if (Image is null) return;
+ if (AllowZoom && SizeMode == SizeModes.Normal)
{
- base.OnPointerPressed(e);
- if (e.Handled
- || _isPanning
- || _isSelecting
- || Image is null) return;
+ // The MouseWheel event can contain multiple "spins" of the wheel so we need to adjust accordingly
+ //double spins = Math.Abs(e.Delta.Y);
+ //Debug.WriteLine(e.GetPosition(this));
+ // TODO: Really should update the source method to handle multiple increments rather than calling it multiple times
+ /*for (int i = 0; i < spins; i++)
+ {*/
+ ProcessMouseZoom(e.Delta.Y > 0, e.GetPosition(ViewPort));
+ //}
+ }
+ }
- var pointer = e.GetCurrentPoint(this);
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
+ {
+ base.OnPointerPressed(e);
+ if (e.Handled
+ || _isPanning
+ || _isSelecting
+ || Image is null) return;
- if (SelectionMode != SelectionModes.None)
- {
- if (!(
- pointer.Properties.IsLeftButtonPressed && (SelectWithMouseButtons & MouseButtons.LeftButton) != 0 ||
- pointer.Properties.IsMiddleButtonPressed && (SelectWithMouseButtons & MouseButtons.MiddleButton) != 0 ||
- pointer.Properties.IsRightButtonPressed && (SelectWithMouseButtons & MouseButtons.RightButton) != 0
- )
- ) return;
- IsSelecting = true;
- }
- else
- {
- if (!(
- pointer.Properties.IsLeftButtonPressed && (PanWithMouseButtons & MouseButtons.LeftButton) != 0 ||
- pointer.Properties.IsMiddleButtonPressed && (PanWithMouseButtons & MouseButtons.MiddleButton) != 0 ||
- pointer.Properties.IsRightButtonPressed && (PanWithMouseButtons & MouseButtons.RightButton) != 0
- )
- || !AutoPan
- || SizeMode != SizeModes.Normal
+ var pointer = e.GetCurrentPoint(this);
- ) return;
+ if (SelectionMode != SelectionModes.None)
+ {
+ if (!(
+ pointer.Properties.IsLeftButtonPressed && (SelectWithMouseButtons & MouseButtons.LeftButton) != 0 ||
+ pointer.Properties.IsMiddleButtonPressed && (SelectWithMouseButtons & MouseButtons.MiddleButton) != 0 ||
+ pointer.Properties.IsRightButtonPressed && (SelectWithMouseButtons & MouseButtons.RightButton) != 0
+ )
+ ) return;
+ IsSelecting = true;
+ }
+ else
+ {
+ if (!(
+ pointer.Properties.IsLeftButtonPressed && (PanWithMouseButtons & MouseButtons.LeftButton) != 0 ||
+ pointer.Properties.IsMiddleButtonPressed && (PanWithMouseButtons & MouseButtons.MiddleButton) != 0 ||
+ pointer.Properties.IsRightButtonPressed && (PanWithMouseButtons & MouseButtons.RightButton) != 0
+ )
+ || !AutoPan
+ || SizeMode != SizeModes.Normal
- IsPanning = true;
- }
+ ) return;
- var location = pointer.Position;
-
- if (location.X > ViewPortSize.Width) return;
- if (location.Y > ViewPortSize.Height) return;
- _startMousePosition = location;
+ IsPanning = true;
}
- protected override void OnPointerReleased(PointerReleasedEventArgs e)
- {
- base.OnPointerReleased(e);
- if (e.Handled) return;
+ var location = pointer.Position;
- IsPanning = false;
- IsSelecting = false;
- }
+ if (location.X > ViewPortSize.Width) return;
+ if (location.Y > ViewPortSize.Height) return;
+ _startMousePosition = location;
+ }
- protected override void OnPointerLeave(PointerEventArgs e)
+ protected override void OnPointerReleased(PointerReleasedEventArgs e)
+ {
+ base.OnPointerReleased(e);
+ if (e.Handled) return;
+
+ IsPanning = false;
+ IsSelecting = false;
+ }
+
+ protected override void OnPointerLeave(PointerEventArgs e)
+ {
+ base.OnPointerLeave(e);
+ PointerPosition = new Point(-1, -1);
+ TriggerRender(true);
+ e.Handled = true;
+ }
+
+ protected override void OnPointerMoved(PointerEventArgs e)
+ {
+ base.OnPointerMoved(e);
+ if (e.Handled) return;
+
+ var pointer = e.GetCurrentPoint(this);
+ PointerPosition = pointer.Position;
+
+ if (!_isPanning && !_isSelecting)
{
- base.OnPointerLeave(e);
- PointerPosition = new Point(-1, -1);
TriggerRender(true);
- e.Handled = true;
+ return;
}
- protected override void OnPointerMoved(PointerEventArgs e)
- {
- base.OnPointerMoved(e);
- if (e.Handled) return;
-
- var pointer = e.GetCurrentPoint(this);
- PointerPosition = pointer.Position;
-
- if (!_isPanning && !_isSelecting)
- {
- TriggerRender(true);
- return;
- }
-
- if (_isPanning)
- {
- double x;
- double y;
-
- if (!InvertMousePan)
- {
- x = _startScrollPosition.X + (_startMousePosition.X - _pointerPosition.X);
- y = _startScrollPosition.Y + (_startMousePosition.Y - _pointerPosition.Y);
- }
- else
- {
- x = (_startScrollPosition.X - (_startMousePosition.X - _pointerPosition.X));
- y = (_startScrollPosition.Y - (_startMousePosition.Y - _pointerPosition.Y));
- }
-
- Offset = new Vector(x, y);
- }
- else if (_isSelecting)
- {
- double x;
- double y;
- double w;
- double h;
-
- var imageOffset = GetImageViewPort().Position;
-
- if (_pointerPosition.X < _startMousePosition.X)
- {
- x = _pointerPosition.X;
- w = _startMousePosition.X - _pointerPosition.X;
- }
- else
- {
- x = _startMousePosition.X;
- w = _pointerPosition.X - _startMousePosition.X;
- }
-
- if (_pointerPosition.Y < _startMousePosition.Y)
- {
- y = _pointerPosition.Y;
- h = _startMousePosition.Y - _pointerPosition.Y;
- }
- else
- {
- y = _startMousePosition.Y;
- h = _pointerPosition.Y - _startMousePosition.Y;
- }
-
- x -= imageOffset.X - Offset.X;
- y -= imageOffset.Y - Offset.Y;
-
- var zoomFactor = ZoomFactor;
- x /= zoomFactor;
- y /= zoomFactor;
- w /= zoomFactor;
- h /= zoomFactor;
-
- if (w != 0 && h != 0)
- {
- SelectionRegion = FitRectangle(new Rect(x, y, w, h));
- }
- }
-
- e.Handled = true;
- }
- #endregion
-
- #region Zoom and Size modes
- private void ProcessMouseZoom(bool isZoomIn, Point cursorPosition)
- => PerformZoom(isZoomIn ? ZoomActions.ZoomIn : ZoomActions.ZoomOut, true, cursorPosition);
-
- ///
- /// Returns an appropriate zoom level based on the specified action, relative to the current zoom level.
- ///
- /// The action to determine the zoom level.
- /// Thrown if an unsupported action is specified.
- private int GetZoomLevel(ZoomActions action)
- {
- var result = action switch
- {
- ZoomActions.None => Zoom,
- ZoomActions.ZoomIn => _zoomLevels.NextZoom(Zoom),
- ZoomActions.ZoomOut => _zoomLevels.PreviousZoom(Zoom),
- ZoomActions.ActualSize => 100,
- _ => throw new ArgumentOutOfRangeException(nameof(action), action, null),
- };
- return result;
- }
-
- ///
- /// Resets the property whilsts retaining the original .
- ///
- protected void RestoreSizeMode()
- {
- if (SizeMode != SizeModes.Normal)
- {
- var previousZoom = Zoom;
- SizeMode = SizeModes.Normal;
- Zoom = previousZoom; // Stop the zoom getting reset to 100% before calculating the new zoom
- }
- }
-
- private void PerformZoom(ZoomActions action, bool preservePosition)
- => PerformZoom(action, preservePosition, CenterPoint);
-
- private void PerformZoom(ZoomActions action, bool preservePosition, Point relativePoint)
- {
- Point currentPixel = PointToImage(relativePoint);
- int currentZoom = Zoom;
- int newZoom = GetZoomLevel(action);
-
- /*if (preservePosition && Zoom != currentZoom)
- CanRender = false;*/
-
- RestoreSizeMode();
- Zoom = newZoom;
-
- if (preservePosition && Zoom != currentZoom)
- {
- ScrollTo(currentPixel, relativePoint);
- }
- }
-
- ///
- /// Zooms into the image
- ///
- public void ZoomIn()
- => ZoomIn(true);
-
- ///
- /// Zooms into the image
- ///
- /// true if the current scrolling position should be preserved relative to the new zoom level, false to reset.
- public void ZoomIn(bool preservePosition)
- {
- PerformZoom(ZoomActions.ZoomIn, preservePosition);
- }
-
- ///
- /// Zooms out of the image
- ///
- public void ZoomOut()
- => ZoomOut(true);
-
- ///
- /// Zooms out of the image
- ///
- /// true if the current scrolling position should be preserved relative to the new zoom level, false to reset.
- public void ZoomOut(bool preservePosition)
- {
- PerformZoom(ZoomActions.ZoomOut, preservePosition);
- }
-
- ///
- /// Zooms to the maximum size for displaying the entire image within the bounds of the control.
- ///
- public void ZoomToFit()
- {
- var image = Image;
- if (image is null) return;
-
- double zoom;
- double aspectRatio;
-
- if (image.Size.Width > image.Size.Height)
- {
- aspectRatio = ViewPortSize.Width / image.Size.Width;
- zoom = aspectRatio * 100.0;
-
- if (ViewPortSize.Height < image.Size.Height * zoom / 100.0)
- {
- aspectRatio = ViewPortSize.Height / image.Size.Height;
- zoom = aspectRatio * 100.0;
- }
- }
- else
- {
- aspectRatio = ViewPortSize.Height / image.Size.Height;
- zoom = aspectRatio * 100.0;
-
- if (ViewPortSize.Width < image.Size.Width * zoom / 100.0)
- {
- aspectRatio = ViewPortSize.Width / image.Size.Width;
- zoom = aspectRatio * 100.0;
- }
- }
-
- Zoom = (int)zoom;
- }
-
- ///
- /// Adjusts the view port to fit the given region
- ///
- /// The X co-ordinate of the selection region.
- /// The Y co-ordinate of the selection region.
- /// The width of the selection region.
- /// The height of the selection region.
- /// Give a margin to rectangle by a value to zoom-out that pixel value
- public void ZoomToRegion(double x, double y, double width, double height, double margin = 0)
- {
- ZoomToRegion(new Rect(x, y, width, height), margin);
- }
-
- ///
- /// Adjusts the view port to fit the given region
- ///
- /// The X co-ordinate of the selection region.
- /// The Y co-ordinate of the selection region.
- /// The width of the selection region.
- /// The height of the selection region.
- /// Give a margin to rectangle by a value to zoom-out that pixel value
- public void ZoomToRegion(int x, int y, int width, int height, double margin = 0)
- {
- ZoomToRegion(new Rect(x, y, width, height), margin);
- }
-
- ///
- /// Adjusts the view port to fit the given region
- ///
- /// The rectangle to fit the view port to.
- /// Give a margin to rectangle by a value to zoom-out that pixel value
- public void ZoomToRegion(Rectangle rectangle, double margin = 0) =>
- ZoomToRegion(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, margin);
-
- ///
- /// Adjusts the view port to fit the given region
- ///
- /// The rectangle to fit the view port to.
- /// Give a margin to rectangle by a value to zoom-out that pixel value
- public void ZoomToRegion(Rect rectangle, double margin = 0)
- {
- if (margin > 0) rectangle = rectangle.Inflate(margin);
- var ratioX = ViewPortSize.Width / rectangle.Width;
- var ratioY = ViewPortSize.Height / rectangle.Height;
- var zoomFactor = Math.Min(ratioX, ratioY);
- var cx = rectangle.X + rectangle.Width / 2;
- var cy = rectangle.Y + rectangle.Height / 2;
-
- CanRender = false;
- Zoom = (int)(zoomFactor * 100); // This function sets the zoom so viewport will change
- CenterAt(new Point(cx, cy)); // If i call this here, it will move to the wrong position due wrong viewport
- }
-
- ///
- /// Zooms to current selection region
- ///
- public void ZoomToSelectionRegion(double margin = 0)
- {
- if (!HaveSelection) return;
- ZoomToRegion(SelectionRegion, margin);
- }
-
- ///
- /// Resets the zoom to 100%.
- ///
- public void PerformActualSize()
- {
- SizeMode = SizeModes.Normal;
- //SetZoom(100, ImageZoomActions.ActualSize | (Zoom < 100 ? ImageZoomActions.ZoomIn : ImageZoomActions.ZoomOut));
- Zoom = 100;
- }
- #endregion
-
- #region Utility methods
- ///
- /// Determines whether the specified point is located within the image view port
- ///
- /// The point.
- ///
- /// true if the specified point is located within the image view port; otherwise, false.
- ///
- public bool IsPointInImage(Point point)
- => GetImageViewPort().Contains(point);
-
- ///
- /// Determines whether the specified point is located within the image view port
- ///
- /// The X co-ordinate of the point to check.
- /// The Y co-ordinate of the point to check.
- ///
- /// true if the specified point is located within the image view port; otherwise, false.
- ///
- public bool IsPointInImage(int x, int y)
- => IsPointInImage(new Point(x, y));
-
- ///
- /// Determines whether the specified point is located within the image view port
- ///
- /// The X co-ordinate of the point to check.
- /// The Y co-ordinate of the point to check.
- ///
- /// true if the specified point is located within the image view port; otherwise, false.
- ///
- public bool IsPointInImage(double x, double y)
- => IsPointInImage(new Point(x, y));
-
- ///
- /// Converts the given client size point to represent a coordinate on the source image.
- ///
- /// The X co-ordinate of the point to convert.
- /// The Y co-ordinate of the point to convert.
- ///
- /// if set to true and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
- ///
- /// Point.Empty if the point could not be matched to the source image, otherwise the new translated point
- public Point PointToImage(double x, double y, bool fitToBounds = true)
- => PointToImage(new Point(x, y), fitToBounds);
-
- ///
- /// Converts the given client size point to represent a coordinate on the source image.
- ///
- /// The X co-ordinate of the point to convert.
- /// The Y co-ordinate of the point to convert.
- ///
- /// if set to true and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
- ///
- /// Point.Empty if the point could not be matched to the source image, otherwise the new translated point
- public Point PointToImage(int x, int y, bool fitToBounds = true)
- {
- return PointToImage(new Point(x, y), fitToBounds);
- }
-
- ///
- /// Converts the given client size point to represent a coordinate on the source image.
- ///
- /// The source point.
- ///
- /// if set to true and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
- ///
- /// Point.Empty if the point could not be matched to the source image, otherwise the new translated point
- public Point PointToImage(Point point, bool fitToBounds = true)
+ if (_isPanning)
{
double x;
double y;
- var viewport = GetImageViewPort();
-
- if (!fitToBounds || viewport.Contains(point))
+ if (!InvertMousePan)
{
- x = (point.X + Offset.X - viewport.X) / ZoomFactor;
- y = (point.Y + Offset.Y - viewport.Y) / ZoomFactor;
-
- var image = Image;
- if (fitToBounds)
- {
- x = Math.Clamp(x, 0, image.Size.Width-1);
- y = Math.Clamp(y, 0, image.Size.Height-1);
- }
+ x = _startScrollPosition.X + (_startMousePosition.X - _pointerPosition.X);
+ y = _startScrollPosition.Y + (_startMousePosition.Y - _pointerPosition.Y);
}
else
{
- x = 0; // Return Point.Empty if we couldn't match
- y = 0;
+ x = (_startScrollPosition.X - (_startMousePosition.X - _pointerPosition.X));
+ y = (_startScrollPosition.Y - (_startMousePosition.Y - _pointerPosition.Y));
}
- return new(x, y);
- }
-
- ///
- /// Returns the source repositioned to include the current image offset and scaled by the current zoom level
- ///
- /// The source to offset.
- /// A which has been repositioned to match the current zoom level and image offset
- public Point GetOffsetPoint(System.Drawing.Point source)
- {
- var offset = GetOffsetPoint(new Point(source.X, source.Y));
-
- return new((int)offset.X, (int)offset.Y);
- }
-
- ///
- /// Returns the source co-ordinates repositioned to include the current image offset and scaled by the current zoom level
- ///
- /// The source X co-ordinate.
- /// The source Y co-ordinate.
- /// A which has been repositioned to match the current zoom level and image offset
- public Point GetOffsetPoint(int x, int y)
- {
- return GetOffsetPoint(new System.Drawing.Point(x, y));
- }
-
- ///
- /// Returns the source co-ordinates repositioned to include the current image offset and scaled by the current zoom level
- ///
- /// The source X co-ordinate.
- /// The source Y co-ordinate.
- /// A which has been repositioned to match the current zoom level and image offset
- public Point GetOffsetPoint(double x, double y)
- {
- return GetOffsetPoint(new Point(x, y));
- }
-
- ///
- /// Returns the source repositioned to include the current image offset and scaled by the current zoom level
- ///
- /// The source to offset.
- /// A which has been repositioned to match the current zoom level and image offset
- public Point GetOffsetPoint(Point source)
- {
- Rect viewport = GetImageViewPort();
- var scaled = GetScaledPoint(source);
- var offsetX = viewport.Left + Offset.X;
- var offsetY = viewport.Top + Offset.Y;
-
- return new(scaled.X + offsetX, scaled.Y + offsetY);
- }
-
- ///
- /// Returns the source scaled according to the current zoom level and repositioned to include the current image offset
- ///
- /// The source to offset.
- /// A which has been resized and repositioned to match the current zoom level and image offset
- public Rect GetOffsetRectangle(Rect source)
- {
- var viewport = GetImageViewPort();
- var scaled = GetScaledRectangle(source);
- var offsetX = viewport.Left - Offset.X;
- var offsetY = viewport.Top - Offset.Y;
-
- return new(new Point(scaled.Left + offsetX, scaled.Top + offsetY), scaled.Size);
- }
-
- ///
- /// Returns the source rectangle scaled according to the current zoom level and repositioned to include the current image offset
- ///
- /// The X co-ordinate of the source rectangle.
- /// The Y co-ordinate of the source rectangle.
- /// The width of the rectangle.
- /// The height of the rectangle.
- /// A which has been resized and repositioned to match the current zoom level and image offset
- public Rectangle GetOffsetRectangle(int x, int y, int width, int height)
- {
- return GetOffsetRectangle(new Rectangle(x, y, width, height));
- }
-
- ///
- /// Returns the source rectangle scaled according to the current zoom level and repositioned to include the current image offset
- ///
- /// The X co-ordinate of the source rectangle.
- /// The Y co-ordinate of the source rectangle.
- /// The width of the rectangle.
- /// The height of the rectangle.
- /// A which has been resized and repositioned to match the current zoom level and image offset
- public Rect GetOffsetRectangle(double x, double y, double width, double height)
- {
- return GetOffsetRectangle(new Rect(x, y, width, height));
- }
-
- ///
- /// Returns the source scaled according to the current zoom level and repositioned to include the current image offset
- ///
- /// The source to offset.
- /// A which has been resized and repositioned to match the current zoom level and image offset
- public Rectangle GetOffsetRectangle(Rectangle source)
- {
- var viewport = GetImageViewPort();
- var scaled = GetScaledRectangle(source);
- var offsetX = viewport.Left + Offset.X;
- var offsetY = viewport.Top + Offset.Y;
-
- return new(new System.Drawing.Point((int)(scaled.Left + offsetX), (int)(scaled.Top + offsetY)), new System.Drawing.Size((int)scaled.Size.Width, (int)scaled.Size.Height));
- }
-
- ///
- /// Fits a given to match image boundaries
- ///
- /// The rectangle.
- ///
- /// A structure remapped to fit the image boundaries
- ///
- public Rectangle FitRectangle(Rectangle rectangle)
- {
- var image = Image;
- if (image is null) return Rectangle.Empty;
- var x = rectangle.X;
- var y = rectangle.Y;
- var w = rectangle.Width;
- var h = rectangle.Height;
-
- if (x < 0)
- {
- x = 0;
- }
-
- if (y < 0)
- {
- y = 0;
- }
-
- if (x + w > image.Size.Width)
- {
- w = (int)(image.Size.Width - x);
- }
-
- if (y + h > image.Size.Height)
- {
- h = (int)(image.Size.Height - y);
- }
-
- return new(x, y, w, h);
- }
-
- ///
- /// Fits a given to match image boundaries
- ///
- /// The rectangle.
- ///
- /// A structure remapped to fit the image boundaries
- ///
- public Rect FitRectangle(Rect rectangle)
- {
- var image = Image;
- if (image is null) return Rect.Empty;
- var x = rectangle.X;
- var y = rectangle.Y;
- var w = rectangle.Width;
- var h = rectangle.Height;
-
- if (x < 0)
- {
- w -= -x;
- x = 0;
- }
-
- if (y < 0)
- {
- h -= -y;
- y = 0;
- }
-
- if (x + w > image.Size.Width)
- {
- w = image.Size.Width - x;
- }
-
- if (y + h > image.Size.Height)
- {
- h = image.Size.Height - y;
- }
-
- return new(x, y, w, h);
- }
- #endregion
-
- #region Navigate / Scroll methods
- ///
- /// Scrolls the control to the given point in the image, offset at the specified display point
- ///
- /// The X co-ordinate of the point to scroll to.
- /// The Y co-ordinate of the point to scroll to.
- /// The X co-ordinate relative to the x parameter.
- /// The Y co-ordinate relative to the y parameter.
- public void ScrollTo(double x, double y, double relativeX, double relativeY)
- => ScrollTo(new Point(x, y), new Point(relativeX, relativeY));
-
- ///
- /// Scrolls the control to the given point in the image, offset at the specified display point
- ///
- /// The X co-ordinate of the point to scroll to.
- /// The Y co-ordinate of the point to scroll to.
- /// The X co-ordinate relative to the x parameter.
- /// The Y co-ordinate relative to the y parameter.
- public void ScrollTo(int x, int y, int relativeX, int relativeY)
- => ScrollTo(new Point(x, y), new Point(relativeX, relativeY));
-
- ///
- /// Scrolls the control to the given point in the image, offset at the specified display point
- ///
- /// The point of the image to attempt to scroll to.
- /// The relative display point to offset scrolling by.
- public void ScrollTo(Point imageLocation, Point relativeDisplayPoint)
- {
- //CanRender = false;
- var zoomFactor = ZoomFactor;
- var x = imageLocation.X * zoomFactor - relativeDisplayPoint.X;
- var y = imageLocation.Y * zoomFactor - relativeDisplayPoint.Y;
-
-
- _canRender = true;
Offset = new Vector(x, y);
+ }
+ else if (_isSelecting)
+ {
+ double x;
+ double y;
+ double w;
+ double h;
- /*Debug.WriteLine(
- $"X/Y: {x},{y} | \n" +
- $"Offset: {Offset} | \n" +
- $"ZoomFactor: {ZoomFactor} | \n" +
- $"Image Location: {imageLocation}\n" +
- $"MAX: {HorizontalScrollBar.Maximum},{VerticalScrollBar.Maximum} \n" +
- $"ViewPort: {Viewport.Width},{Viewport.Height} \n" +
- $"Container: {HorizontalScrollBar.ViewportSize},{VerticalScrollBar.ViewportSize} \n" +
- $"Relative: {relativeDisplayPoint}");*/
+ var imageOffset = GetImageViewPort().Position;
+
+ if (_pointerPosition.X < _startMousePosition.X)
+ {
+ x = _pointerPosition.X;
+ w = _startMousePosition.X - _pointerPosition.X;
+ }
+ else
+ {
+ x = _startMousePosition.X;
+ w = _pointerPosition.X - _startMousePosition.X;
+ }
+
+ if (_pointerPosition.Y < _startMousePosition.Y)
+ {
+ y = _pointerPosition.Y;
+ h = _startMousePosition.Y - _pointerPosition.Y;
+ }
+ else
+ {
+ y = _startMousePosition.Y;
+ h = _pointerPosition.Y - _startMousePosition.Y;
+ }
+
+ x -= imageOffset.X - Offset.X;
+ y -= imageOffset.Y - Offset.Y;
+
+ var zoomFactor = ZoomFactor;
+ x /= zoomFactor;
+ y /= zoomFactor;
+ w /= zoomFactor;
+ h /= zoomFactor;
+
+ if (w != 0 && h != 0)
+ {
+ SelectionRegion = FitRectangle(new Rect(x, y, w, h));
+ }
}
- ///
- /// Centers the given point in the image in the center of the control
- ///
- /// The point of the image to attempt to center.
- public void CenterAt(System.Drawing.Point imageLocation)
- => ScrollTo(new Point(imageLocation.X, imageLocation.Y), new Point(ViewPortSize.Width / 2, ViewPortSize.Height / 2));
+ e.Handled = true;
+ }
+ #endregion
- ///
- /// Centers the given point in the image in the center of the control
- ///
- /// The point of the image to attempt to center.
- public void CenterAt(Point imageLocation)
- => ScrollTo(imageLocation, new Point(ViewPortSize.Width / 2, ViewPortSize.Height / 2));
+ #region Zoom and Size modes
+ private void ProcessMouseZoom(bool isZoomIn, Point cursorPosition)
+ => PerformZoom(isZoomIn ? ZoomActions.ZoomIn : ZoomActions.ZoomOut, true, cursorPosition);
- ///
- /// Centers the given point in the image in the center of the control
- ///
- /// The X co-ordinate of the point to center.
- /// The Y co-ordinate of the point to center.
- public void CenterAt(int x, int y)
- => CenterAt(new Point(x, y));
-
- ///
- /// Centers the given point in the image in the center of the control
- ///
- /// The X co-ordinate of the point to center.
- /// The Y co-ordinate of the point to center.
- public void CenterAt(double x, double y)
- => CenterAt(new Point(x, y));
-
- ///
- /// Resets the viewport to show the center of the image.
- ///
- public void CenterToImage()
+ ///
+ /// Returns an appropriate zoom level based on the specified action, relative to the current zoom level.
+ ///
+ /// The action to determine the zoom level.
+ /// Thrown if an unsupported action is specified.
+ private int GetZoomLevel(ZoomActions action)
+ {
+ var result = action switch
{
- Offset = new Vector(HorizontalScrollBar.Maximum / 2, VerticalScrollBar.Maximum / 2);
+ ZoomActions.None => Zoom,
+ ZoomActions.ZoomIn => _zoomLevels.NextZoom(Zoom),
+ ZoomActions.ZoomOut => _zoomLevels.PreviousZoom(Zoom),
+ ZoomActions.ActualSize => 100,
+ _ => throw new ArgumentOutOfRangeException(nameof(action), action, null),
+ };
+ return result;
+ }
+
+ ///
+ /// Resets the property whilsts retaining the original .
+ ///
+ protected void RestoreSizeMode()
+ {
+ if (SizeMode != SizeModes.Normal)
+ {
+ var previousZoom = Zoom;
+ SizeMode = SizeModes.Normal;
+ Zoom = previousZoom; // Stop the zoom getting reset to 100% before calculating the new zoom
}
- #endregion
+ }
- #region Selection / ROI methods
+ private void PerformZoom(ZoomActions action, bool preservePosition)
+ => PerformZoom(action, preservePosition, CenterPoint);
- ///
- /// Returns the source scaled according to the current zoom level
- ///
- /// The X co-ordinate of the point to scale.
- /// The Y co-ordinate of the point to scale.
- /// A which has been scaled to match the current zoom level
- public Point GetScaledPoint(int x, int y)
+ private void PerformZoom(ZoomActions action, bool preservePosition, Point relativePoint)
+ {
+ Point currentPixel = PointToImage(relativePoint);
+ int currentZoom = Zoom;
+ int newZoom = GetZoomLevel(action);
+
+ /*if (preservePosition && Zoom != currentZoom)
+ CanRender = false;*/
+
+ RestoreSizeMode();
+ Zoom = newZoom;
+
+ if (preservePosition && Zoom != currentZoom)
{
- return GetScaledPoint(new Point(x, y));
+ ScrollTo(currentPixel, relativePoint);
+ }
+ }
+
+ ///
+ /// Zooms into the image
+ ///
+ public void ZoomIn()
+ => ZoomIn(true);
+
+ ///
+ /// Zooms into the image
+ ///
+ /// true if the current scrolling position should be preserved relative to the new zoom level, false to reset.
+ public void ZoomIn(bool preservePosition)
+ {
+ PerformZoom(ZoomActions.ZoomIn, preservePosition);
+ }
+
+ ///
+ /// Zooms out of the image
+ ///
+ public void ZoomOut()
+ => ZoomOut(true);
+
+ ///
+ /// Zooms out of the image
+ ///
+ /// true if the current scrolling position should be preserved relative to the new zoom level, false to reset.
+ public void ZoomOut(bool preservePosition)
+ {
+ PerformZoom(ZoomActions.ZoomOut, preservePosition);
+ }
+
+ ///
+ /// Zooms to the maximum size for displaying the entire image within the bounds of the control.
+ ///
+ public void ZoomToFit()
+ {
+ var image = Image;
+ if (image is null) return;
+
+ double zoom;
+ double aspectRatio;
+
+ if (image.Size.Width > image.Size.Height)
+ {
+ aspectRatio = ViewPortSize.Width / image.Size.Width;
+ zoom = aspectRatio * 100.0;
+
+ if (ViewPortSize.Height < image.Size.Height * zoom / 100.0)
+ {
+ aspectRatio = ViewPortSize.Height / image.Size.Height;
+ zoom = aspectRatio * 100.0;
+ }
+ }
+ else
+ {
+ aspectRatio = ViewPortSize.Height / image.Size.Height;
+ zoom = aspectRatio * 100.0;
+
+ if (ViewPortSize.Width < image.Size.Width * zoom / 100.0)
+ {
+ aspectRatio = ViewPortSize.Width / image.Size.Width;
+ zoom = aspectRatio * 100.0;
+ }
}
- ///
- /// Returns the source scaled according to the current zoom level
- ///
- /// The X co-ordinate of the point to scale.
- /// The Y co-ordinate of the point to scale.
- /// A which has been scaled to match the current zoom level
- public PointF GetScaledPoint(float x, float y)
- {
- return GetScaledPoint(new PointF(x, y));
- }
+ Zoom = (int)zoom;
+ }
- ///
- /// Returns the source scaled according to the current zoom level
- ///
- /// The source to scale.
- /// A which has been scaled to match the current zoom level
- public Point GetScaledPoint(Point source)
- {
- return new(source.X * ZoomFactor, source.Y * ZoomFactor);
- }
+ ///
+ /// Adjusts the view port to fit the given region
+ ///
+ /// The X co-ordinate of the selection region.
+ /// The Y co-ordinate of the selection region.
+ /// The width of the selection region.
+ /// The height of the selection region.
+ /// Give a margin to rectangle by a value to zoom-out that pixel value
+ public void ZoomToRegion(double x, double y, double width, double height, double margin = 0)
+ {
+ ZoomToRegion(new Rect(x, y, width, height), margin);
+ }
- ///
- /// Returns the source scaled according to the current zoom level
- ///
- /// The source to scale.
- /// A which has been scaled to match the current zoom level
- public PointF GetScaledPoint(PointF source)
- {
- return new((float)(source.X * ZoomFactor), (float)(source.Y * ZoomFactor));
- }
+ ///
+ /// Adjusts the view port to fit the given region
+ ///
+ /// The X co-ordinate of the selection region.
+ /// The Y co-ordinate of the selection region.
+ /// The width of the selection region.
+ /// The height of the selection region.
+ /// Give a margin to rectangle by a value to zoom-out that pixel value
+ public void ZoomToRegion(int x, int y, int width, int height, double margin = 0)
+ {
+ ZoomToRegion(new Rect(x, y, width, height), margin);
+ }
- ///
- /// Returns the source rectangle scaled according to the current zoom level
- ///
- /// The X co-ordinate of the source rectangle.
- /// The Y co-ordinate of the source rectangle.
- /// The width of the rectangle.
- /// The height of the rectangle.
- /// A which has been scaled to match the current zoom level
- public Rect GetScaledRectangle(int x, int y, int width, int height)
- {
- return GetScaledRectangle(new Rect(x, y, width, height));
- }
+ ///
+ /// Adjusts the view port to fit the given region
+ ///
+ /// The rectangle to fit the view port to.
+ /// Give a margin to rectangle by a value to zoom-out that pixel value
+ public void ZoomToRegion(Rectangle rectangle, double margin = 0) =>
+ ZoomToRegion(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, margin);
- ///
- /// Returns the source rectangle scaled according to the current zoom level
- ///
- /// The X co-ordinate of the source rectangle.
- /// The Y co-ordinate of the source rectangle.
- /// The width of the rectangle.
- /// The height of the rectangle.
- /// A which has been scaled to match the current zoom level
- public RectangleF GetScaledRectangle(float x, float y, float width, float height)
- {
- return GetScaledRectangle(new RectangleF(x, y, width, height));
- }
+ ///
+ /// Adjusts the view port to fit the given region
+ ///
+ /// The rectangle to fit the view port to.
+ /// Give a margin to rectangle by a value to zoom-out that pixel value
+ public void ZoomToRegion(Rect rectangle, double margin = 0)
+ {
+ if (margin > 0) rectangle = rectangle.Inflate(margin);
+ var ratioX = ViewPortSize.Width / rectangle.Width;
+ var ratioY = ViewPortSize.Height / rectangle.Height;
+ var zoomFactor = Math.Min(ratioX, ratioY);
+ var cx = rectangle.X + rectangle.Width / 2;
+ var cy = rectangle.Y + rectangle.Height / 2;
- ///
- /// Returns the source rectangle scaled according to the current zoom level
- ///
- /// The location of the source rectangle.
- /// The size of the source rectangle.
- /// A which has been scaled to match the current zoom level
- public Rect GetScaledRectangle(Point location, Size size)
- {
- return GetScaledRectangle(new Rect(location, size));
- }
+ CanRender = false;
+ Zoom = (int)(zoomFactor * 100); // This function sets the zoom so viewport will change
+ CenterAt(new Point(cx, cy)); // If i call this here, it will move to the wrong position due wrong viewport
+ }
- ///
- /// Returns the source rectangle scaled according to the current zoom level
- ///
- /// The location of the source rectangle.
- /// The size of the source rectangle.
- /// A which has been scaled to match the current zoom level
- public RectangleF GetScaledRectangle(PointF location, SizeF size)
- {
- return GetScaledRectangle(new RectangleF(location, size));
- }
+ ///
+ /// Zooms to current selection region
+ ///
+ public void ZoomToSelectionRegion(double margin = 0)
+ {
+ if (!HaveSelection) return;
+ ZoomToRegion(SelectionRegion, margin);
+ }
- ///
- /// Returns the source scaled according to the current zoom level
- ///
- /// The source to scale.
- /// A which has been scaled to match the current zoom level
- public Rect GetScaledRectangle(Rect source)
- {
- return new(source.Left * ZoomFactor, source.Top * ZoomFactor, source.Width * ZoomFactor, source.Height * ZoomFactor);
- }
+ ///
+ /// Resets the zoom to 100%.
+ ///
+ public void PerformActualSize()
+ {
+ SizeMode = SizeModes.Normal;
+ //SetZoom(100, ImageZoomActions.ActualSize | (Zoom < 100 ? ImageZoomActions.ZoomIn : ImageZoomActions.ZoomOut));
+ Zoom = 100;
+ }
+ #endregion
- ///
- /// Returns the source scaled according to the current zoom level
- ///
- /// The source to scale.
- /// A which has been scaled to match the current zoom level
- public RectangleF GetScaledRectangle(RectangleF source)
- {
- return new((float)(source.Left * ZoomFactor), (float)(source.Top * ZoomFactor), (float)(source.Width * ZoomFactor), (float)(source.Height * ZoomFactor));
- }
+ #region Utility methods
+ ///
+ /// Determines whether the specified point is located within the image view port
+ ///
+ /// The point.
+ ///
+ /// true if the specified point is located within the image view port; otherwise, false.
+ ///
+ public bool IsPointInImage(Point point)
+ => GetImageViewPort().Contains(point);
- ///
- /// Returns the source size scaled according to the current zoom level
- ///
- /// The width of the size to scale.
- /// The height of the size to scale.
- /// A which has been resized to match the current zoom level
- public SizeF GetScaledSize(float width, float height)
- {
- return GetScaledSize(new SizeF(width, height));
- }
+ ///
+ /// Determines whether the specified point is located within the image view port
+ ///
+ /// The X co-ordinate of the point to check.
+ /// The Y co-ordinate of the point to check.
+ ///
+ /// true if the specified point is located within the image view port; otherwise, false.
+ ///
+ public bool IsPointInImage(int x, int y)
+ => IsPointInImage(new Point(x, y));
- ///
- /// Returns the source size scaled according to the current zoom level
- ///
- /// The width of the size to scale.
- /// The height of the size to scale.
- /// A which has been resized to match the current zoom level
- public Size GetScaledSize(int width, int height)
- {
- return GetScaledSize(new Size(width, height));
- }
+ ///
+ /// Determines whether the specified point is located within the image view port
+ ///
+ /// The X co-ordinate of the point to check.
+ /// The Y co-ordinate of the point to check.
+ ///
+ /// true if the specified point is located within the image view port; otherwise, false.
+ ///
+ public bool IsPointInImage(double x, double y)
+ => IsPointInImage(new Point(x, y));
- ///
- /// Returns the source scaled according to the current zoom level
- ///
- /// The source to scale.
- /// A which has been resized to match the current zoom level
- public SizeF GetScaledSize(SizeF source)
- {
- return new((float)(source.Width * ZoomFactor), (float)(source.Height * ZoomFactor));
- }
+ ///
+ /// Converts the given client size point to represent a coordinate on the source image.
+ ///
+ /// The X co-ordinate of the point to convert.
+ /// The Y co-ordinate of the point to convert.
+ ///
+ /// if set to true and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
+ ///
+ /// Point.Empty if the point could not be matched to the source image, otherwise the new translated point
+ public Point PointToImage(double x, double y, bool fitToBounds = true)
+ => PointToImage(new Point(x, y), fitToBounds);
- ///
- /// Returns the source scaled according to the current zoom level
- ///
- /// The source to scale.
- /// A which has been resized to match the current zoom level
- public Size GetScaledSize(Size source)
- {
- return new(source.Width * ZoomFactor, source.Height * ZoomFactor);
- }
+ ///
+ /// Converts the given client size point to represent a coordinate on the source image.
+ ///
+ /// The X co-ordinate of the point to convert.
+ /// The Y co-ordinate of the point to convert.
+ ///
+ /// if set to true and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
+ ///
+ /// Point.Empty if the point could not be matched to the source image, otherwise the new translated point
+ public Point PointToImage(int x, int y, bool fitToBounds = true)
+ {
+ return PointToImage(new Point(x, y), fitToBounds);
+ }
- ///
- /// Creates a selection region which encompasses the entire image
- ///
- /// Thrown if no image is currently set
- public void SelectAll()
+ ///
+ /// Converts the given client size point to represent a coordinate on the source image.
+ ///
+ /// The source point.
+ ///
+ /// if set to true and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
+ ///
+ /// Point.Empty if the point could not be matched to the source image, otherwise the new translated point
+ public Point PointToImage(Point point, bool fitToBounds = true)
+ {
+ double x;
+ double y;
+
+ var viewport = GetImageViewPort();
+
+ if (!fitToBounds || viewport.Contains(point))
{
+ x = (point.X + Offset.X - viewport.X) / ZoomFactor;
+ y = (point.Y + Offset.Y - viewport.Y) / ZoomFactor;
+
var image = Image;
- if (image is null) return;
- SelectionRegion = new Rect(0, 0, image.Size.Width, image.Size.Height);
+ if (fitToBounds)
+ {
+ x = Math.Clamp(x, 0, image!.Size.Width-1);
+ y = Math.Clamp(y, 0, image.Size.Height-1);
+ }
}
-
- ///
- /// Clears any existing selection region
- ///
- public void SelectNone()
+ else
{
- SelectionRegion = Rect.Empty;
+ x = 0; // Return Point.Empty if we couldn't match
+ y = 0;
}
- #endregion
+ return new(x, y);
+ }
+
+ ///
+ /// Returns the source repositioned to include the current image offset and scaled by the current zoom level
+ ///
+ /// The source to offset.
+ /// A which has been repositioned to match the current zoom level and image offset
+ public Point GetOffsetPoint(System.Drawing.Point source)
+ {
+ var offset = GetOffsetPoint(new Point(source.X, source.Y));
+
+ return new((int)offset.X, (int)offset.Y);
+ }
+
+ ///
+ /// Returns the source co-ordinates repositioned to include the current image offset and scaled by the current zoom level
+ ///
+ /// The source X co-ordinate.
+ /// The source Y co-ordinate.
+ /// A which has been repositioned to match the current zoom level and image offset
+ public Point GetOffsetPoint(int x, int y)
+ {
+ return GetOffsetPoint(new System.Drawing.Point(x, y));
+ }
+
+ ///
+ /// Returns the source co-ordinates repositioned to include the current image offset and scaled by the current zoom level
+ ///
+ /// The source X co-ordinate.
+ /// The source Y co-ordinate.
+ /// A which has been repositioned to match the current zoom level and image offset
+ public Point GetOffsetPoint(double x, double y)
+ {
+ return GetOffsetPoint(new Point(x, y));
+ }
+
+ ///
+ /// Returns the source repositioned to include the current image offset and scaled by the current zoom level
+ ///
+ /// The source to offset.
+ /// A which has been repositioned to match the current zoom level and image offset
+ public Point GetOffsetPoint(Point source)
+ {
+ Rect viewport = GetImageViewPort();
+ var scaled = GetScaledPoint(source);
+ var offsetX = viewport.Left + Offset.X;
+ var offsetY = viewport.Top + Offset.Y;
+
+ return new(scaled.X + offsetX, scaled.Y + offsetY);
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level and repositioned to include the current image offset
+ ///
+ /// The source to offset.
+ /// A which has been resized and repositioned to match the current zoom level and image offset
+ public Rect GetOffsetRectangle(Rect source)
+ {
+ var viewport = GetImageViewPort();
+ var scaled = GetScaledRectangle(source);
+ var offsetX = viewport.Left - Offset.X;
+ var offsetY = viewport.Top - Offset.Y;
+
+ return new(new Point(scaled.Left + offsetX, scaled.Top + offsetY), scaled.Size);
+ }
+
+ ///
+ /// Returns the source rectangle scaled according to the current zoom level and repositioned to include the current image offset
+ ///
+ /// The X co-ordinate of the source rectangle.
+ /// The Y co-ordinate of the source rectangle.
+ /// The width of the rectangle.
+ /// The height of the rectangle.
+ /// A which has been resized and repositioned to match the current zoom level and image offset
+ public Rectangle GetOffsetRectangle(int x, int y, int width, int height)
+ {
+ return GetOffsetRectangle(new Rectangle(x, y, width, height));
+ }
+
+ ///
+ /// Returns the source rectangle scaled according to the current zoom level and repositioned to include the current image offset
+ ///
+ /// The X co-ordinate of the source rectangle.
+ /// The Y co-ordinate of the source rectangle.
+ /// The width of the rectangle.
+ /// The height of the rectangle.
+ /// A which has been resized and repositioned to match the current zoom level and image offset
+ public Rect GetOffsetRectangle(double x, double y, double width, double height)
+ {
+ return GetOffsetRectangle(new Rect(x, y, width, height));
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level and repositioned to include the current image offset
+ ///
+ /// The source to offset.
+ /// A which has been resized and repositioned to match the current zoom level and image offset
+ public Rectangle GetOffsetRectangle(Rectangle source)
+ {
+ var viewport = GetImageViewPort();
+ var scaled = GetScaledRectangle(source);
+ var offsetX = viewport.Left + Offset.X;
+ var offsetY = viewport.Top + Offset.Y;
+
+ return new(new System.Drawing.Point((int)(scaled.Left + offsetX), (int)(scaled.Top + offsetY)), new System.Drawing.Size((int)scaled.Size.Width, (int)scaled.Size.Height));
+ }
+
+ ///
+ /// Fits a given to match image boundaries
+ ///
+ /// The rectangle.
+ ///
+ /// A structure remapped to fit the image boundaries
+ ///
+ public Rectangle FitRectangle(Rectangle rectangle)
+ {
+ var image = Image;
+ if (image is null) return Rectangle.Empty;
+ var x = rectangle.X;
+ var y = rectangle.Y;
+ var w = rectangle.Width;
+ var h = rectangle.Height;
+
+ if (x < 0)
+ {
+ x = 0;
+ }
+
+ if (y < 0)
+ {
+ y = 0;
+ }
+
+ if (x + w > image.Size.Width)
+ {
+ w = (int)(image.Size.Width - x);
+ }
+
+ if (y + h > image.Size.Height)
+ {
+ h = (int)(image.Size.Height - y);
+ }
+
+ return new(x, y, w, h);
+ }
+
+ ///
+ /// Fits a given to match image boundaries
+ ///
+ /// The rectangle.
+ ///
+ /// A structure remapped to fit the image boundaries
+ ///
+ public Rect FitRectangle(Rect rectangle)
+ {
+ var image = Image;
+ if (image is null) return Rect.Empty;
+ var x = rectangle.X;
+ var y = rectangle.Y;
+ var w = rectangle.Width;
+ var h = rectangle.Height;
+
+ if (x < 0)
+ {
+ w -= -x;
+ x = 0;
+ }
+
+ if (y < 0)
+ {
+ h -= -y;
+ y = 0;
+ }
+
+ if (x + w > image.Size.Width)
+ {
+ w = image.Size.Width - x;
+ }
+
+ if (y + h > image.Size.Height)
+ {
+ h = image.Size.Height - y;
+ }
+
+ return new(x, y, w, h);
+ }
+ #endregion
+
+ #region Navigate / Scroll methods
+ ///
+ /// Scrolls the control to the given point in the image, offset at the specified display point
+ ///
+ /// The X co-ordinate of the point to scroll to.
+ /// The Y co-ordinate of the point to scroll to.
+ /// The X co-ordinate relative to the x parameter.
+ /// The Y co-ordinate relative to the y parameter.
+ public void ScrollTo(double x, double y, double relativeX, double relativeY)
+ => ScrollTo(new Point(x, y), new Point(relativeX, relativeY));
+
+ ///
+ /// Scrolls the control to the given point in the image, offset at the specified display point
+ ///
+ /// The X co-ordinate of the point to scroll to.
+ /// The Y co-ordinate of the point to scroll to.
+ /// The X co-ordinate relative to the x parameter.
+ /// The Y co-ordinate relative to the y parameter.
+ public void ScrollTo(int x, int y, int relativeX, int relativeY)
+ => ScrollTo(new Point(x, y), new Point(relativeX, relativeY));
+
+ ///
+ /// Scrolls the control to the given point in the image, offset at the specified display point
+ ///
+ /// The point of the image to attempt to scroll to.
+ /// The relative display point to offset scrolling by.
+ public void ScrollTo(Point imageLocation, Point relativeDisplayPoint)
+ {
+ //CanRender = false;
+ var zoomFactor = ZoomFactor;
+ var x = imageLocation.X * zoomFactor - relativeDisplayPoint.X;
+ var y = imageLocation.Y * zoomFactor - relativeDisplayPoint.Y;
+
+
+ _canRender = true;
+ Offset = new Vector(x, y);
+
+ /*Debug.WriteLine(
+ $"X/Y: {x},{y} | \n" +
+ $"Offset: {Offset} | \n" +
+ $"ZoomFactor: {ZoomFactor} | \n" +
+ $"Image Location: {imageLocation}\n" +
+ $"MAX: {HorizontalScrollBar.Maximum},{VerticalScrollBar.Maximum} \n" +
+ $"ViewPort: {Viewport.Width},{Viewport.Height} \n" +
+ $"Container: {HorizontalScrollBar.ViewportSize},{VerticalScrollBar.ViewportSize} \n" +
+ $"Relative: {relativeDisplayPoint}");*/
+ }
+
+ ///
+ /// Centers the given point in the image in the center of the control
+ ///
+ /// The point of the image to attempt to center.
+ public void CenterAt(System.Drawing.Point imageLocation)
+ => ScrollTo(new Point(imageLocation.X, imageLocation.Y), new Point(ViewPortSize.Width / 2, ViewPortSize.Height / 2));
+
+ ///
+ /// Centers the given point in the image in the center of the control
+ ///
+ /// The point of the image to attempt to center.
+ public void CenterAt(Point imageLocation)
+ => ScrollTo(imageLocation, new Point(ViewPortSize.Width / 2, ViewPortSize.Height / 2));
+
+ ///
+ /// Centers the given point in the image in the center of the control
+ ///
+ /// The X co-ordinate of the point to center.
+ /// The Y co-ordinate of the point to center.
+ public void CenterAt(int x, int y)
+ => CenterAt(new Point(x, y));
+
+ ///
+ /// Centers the given point in the image in the center of the control
+ ///
+ /// The X co-ordinate of the point to center.
+ /// The Y co-ordinate of the point to center.
+ public void CenterAt(double x, double y)
+ => CenterAt(new Point(x, y));
+
+ ///
+ /// Resets the viewport to show the center of the image.
+ ///
+ public void CenterToImage()
+ {
+ Offset = new Vector(HorizontalScrollBar.Maximum / 2, VerticalScrollBar.Maximum / 2);
+ }
+ #endregion
+
+ #region Selection / ROI methods
+
+ ///
+ /// Returns the source scaled according to the current zoom level
+ ///
+ /// The X co-ordinate of the point to scale.
+ /// The Y co-ordinate of the point to scale.
+ /// A which has been scaled to match the current zoom level
+ public Point GetScaledPoint(int x, int y)
+ {
+ return GetScaledPoint(new Point(x, y));
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level
+ ///
+ /// The X co-ordinate of the point to scale.
+ /// The Y co-ordinate of the point to scale.
+ /// A which has been scaled to match the current zoom level
+ public PointF GetScaledPoint(float x, float y)
+ {
+ return GetScaledPoint(new PointF(x, y));
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level
+ ///
+ /// The source to scale.
+ /// A which has been scaled to match the current zoom level
+ public Point GetScaledPoint(Point source)
+ {
+ return new(source.X * ZoomFactor, source.Y * ZoomFactor);
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level
+ ///
+ /// The source to scale.
+ /// A which has been scaled to match the current zoom level
+ public PointF GetScaledPoint(PointF source)
+ {
+ return new((float)(source.X * ZoomFactor), (float)(source.Y * ZoomFactor));
+ }
+
+ ///
+ /// Returns the source rectangle scaled according to the current zoom level
+ ///
+ /// The X co-ordinate of the source rectangle.
+ /// The Y co-ordinate of the source rectangle.
+ /// The width of the rectangle.
+ /// The height of the rectangle.
+ /// A which has been scaled to match the current zoom level
+ public Rect GetScaledRectangle(int x, int y, int width, int height)
+ {
+ return GetScaledRectangle(new Rect(x, y, width, height));
+ }
+
+ ///
+ /// Returns the source rectangle scaled according to the current zoom level
+ ///
+ /// The X co-ordinate of the source rectangle.
+ /// The Y co-ordinate of the source rectangle.
+ /// The width of the rectangle.
+ /// The height of the rectangle.
+ /// A which has been scaled to match the current zoom level
+ public RectangleF GetScaledRectangle(float x, float y, float width, float height)
+ {
+ return GetScaledRectangle(new RectangleF(x, y, width, height));
+ }
+
+ ///
+ /// Returns the source rectangle scaled according to the current zoom level
+ ///
+ /// The location of the source rectangle.
+ /// The size of the source rectangle.
+ /// A which has been scaled to match the current zoom level
+ public Rect GetScaledRectangle(Point location, Size size)
+ {
+ return GetScaledRectangle(new Rect(location, size));
+ }
+
+ ///
+ /// Returns the source rectangle scaled according to the current zoom level
+ ///
+ /// The location of the source rectangle.
+ /// The size of the source rectangle.
+ /// A which has been scaled to match the current zoom level
+ public RectangleF GetScaledRectangle(PointF location, SizeF size)
+ {
+ return GetScaledRectangle(new RectangleF(location, size));
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level
+ ///
+ /// The source to scale.
+ /// A which has been scaled to match the current zoom level
+ public Rect GetScaledRectangle(Rect source)
+ {
+ return new(source.Left * ZoomFactor, source.Top * ZoomFactor, source.Width * ZoomFactor, source.Height * ZoomFactor);
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level
+ ///
+ /// The source to scale.
+ /// A which has been scaled to match the current zoom level
+ public RectangleF GetScaledRectangle(RectangleF source)
+ {
+ return new((float)(source.Left * ZoomFactor), (float)(source.Top * ZoomFactor), (float)(source.Width * ZoomFactor), (float)(source.Height * ZoomFactor));
+ }
+
+ ///
+ /// Returns the source size scaled according to the current zoom level
+ ///
+ /// The width of the size to scale.
+ /// The height of the size to scale.
+ /// A which has been resized to match the current zoom level
+ public SizeF GetScaledSize(float width, float height)
+ {
+ return GetScaledSize(new SizeF(width, height));
+ }
+
+ ///
+ /// Returns the source size scaled according to the current zoom level
+ ///
+ /// The width of the size to scale.
+ /// The height of the size to scale.
+ /// A which has been resized to match the current zoom level
+ public Size GetScaledSize(int width, int height)
+ {
+ return GetScaledSize(new Size(width, height));
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level
+ ///
+ /// The source to scale.
+ /// A which has been resized to match the current zoom level
+ public SizeF GetScaledSize(SizeF source)
+ {
+ return new((float)(source.Width * ZoomFactor), (float)(source.Height * ZoomFactor));
+ }
+
+ ///
+ /// Returns the source scaled according to the current zoom level
+ ///
+ /// The source to scale.
+ /// A which has been resized to match the current zoom level
+ public Size GetScaledSize(Size source)
+ {
+ return new(source.Width * ZoomFactor, source.Height * ZoomFactor);
+ }
+
+ ///
+ /// Creates a selection region which encompasses the entire image
+ ///
+ /// Thrown if no image is currently set
+ public void SelectAll()
+ {
+ var image = Image;
+ if (image is null) return;
+ SelectionRegion = new Rect(0, 0, image.Size.Width, image.Size.Height);
+ }
+
+ ///
+ /// Clears any existing selection region
+ ///
+ public void SelectNone()
+ {
+ SelectionRegion = Rect.Empty;
+ }
+
+ #endregion
- #region Viewport and image region methods
- ///
- /// Gets the source image region.
- ///
- ///
- public Rect GetSourceImageRegion()
+ #region Viewport and image region methods
+ ///
+ /// Gets the source image region.
+ ///
+ ///
+ public Rect GetSourceImageRegion()
+ {
+ var image = Image;
+ if (image is null) return Rect.Empty;
+
+ switch (SizeMode)
{
- var image = Image;
- if (image is null) return Rect.Empty;
-
- switch (SizeMode)
- {
- case SizeModes.Normal:
- var offset = Offset;
- var viewPort = GetImageViewPort();
- var zoomFactor = ZoomFactor;
- double sourceLeft = (offset.X / zoomFactor);
- double sourceTop = (offset.Y / zoomFactor);
- double sourceWidth = (viewPort.Width / zoomFactor);
- double sourceHeight = (viewPort.Height / zoomFactor);
-
- return new(sourceLeft, sourceTop, sourceWidth, sourceHeight);
- }
-
- return new(0, 0, image.Size.Width, image.Size.Height);
+ case SizeModes.Normal:
+ var offset = Offset;
+ var viewPort = GetImageViewPort();
+ var zoomFactor = ZoomFactor;
+ double sourceLeft = (offset.X / zoomFactor);
+ double sourceTop = (offset.Y / zoomFactor);
+ double sourceWidth = (viewPort.Width / zoomFactor);
+ double sourceHeight = (viewPort.Height / zoomFactor);
+ return new(sourceLeft, sourceTop, sourceWidth, sourceHeight);
}
- ///
- /// Gets the image view port.
- ///
- ///
- public Rect GetImageViewPort()
- {
- if (!IsImageLoaded || (ViewPortSize.Width == 0 && ViewPortSize.Height == 0)) return Rect.Empty;
-
- double xOffset = 0;
- double yOffset = 0;
- double width = 0;
- double height = 0;
-
- switch (SizeMode)
- {
- case SizeModes.Normal:
- if (AutoCenter)
- {
- xOffset = (!IsHorizontalBarVisible ? (ViewPortSize.Width - ScaledImageWidth) / 2 : 0);
- yOffset = (!IsVerticalBarVisible ? (ViewPortSize.Height - ScaledImageHeight) / 2 : 0);
- }
-
- width = Math.Min(ScaledImageWidth - Math.Abs(Offset.X), ViewPortSize.Width);
- height = Math.Min(ScaledImageHeight - Math.Abs(Offset.Y), ViewPortSize.Height);
- break;
- case SizeModes.Stretch:
- width = ViewPortSize.Width;
- height = ViewPortSize.Height;
- break;
- case SizeModes.Fit:
- var image = Image;
- double scaleFactor = Math.Min(ViewPortSize.Width / image.Size.Width, ViewPortSize.Height / image.Size.Height);
-
- width = Math.Floor(image.Size.Width * scaleFactor);
- height = Math.Floor(image.Size.Height * scaleFactor);
-
- if (AutoCenter)
- {
- xOffset = (ViewPortSize.Width - width) / 2;
- yOffset = (ViewPortSize.Height - height) / 2;
- }
-
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(SizeMode), SizeMode, null);
- }
-
- return new(xOffset, yOffset, width, height);
- }
- #endregion
-
- #region Image methods
- public void LoadImage(string path)
- {
- Image = new Bitmap(path);
- }
-
- public Bitmap GetSelectedBitmap()
- {
- var image = ImageAsWriteableBitmap;
- if (image is null || !HaveSelection) return null;
- var selection = SelectionRegionNet;
- var pixelSize = SelectionPixelSize;
- using var frameBuffer = image.Lock();
-
- var newBitmap = new WriteableBitmap(pixelSize, image.Dpi, frameBuffer.Format, AlphaFormat.Unpremul);
- using var newFrameBuffer = newBitmap.Lock();
-
- int i = 0;
-
- unsafe
- {
- var inputPixels = (uint*) (void*) frameBuffer.Address;
- var targetPixels = (uint*) (void*) newFrameBuffer.Address;
-
- for (int y = selection.Y; y < selection.Bottom; y++)
- {
- var thisY = y * frameBuffer.Size.Width;
- for (int x = selection.X; x < selection.Right; x++)
- {
- targetPixels[i++] = inputPixels[thisY + x];
- }
- }
- }
-
- return newBitmap;
- }
- #endregion
+ return new(0, 0, image.Size.Width, image.Size.Height);
}
-}
+
+ ///
+ /// Gets the image view port.
+ ///
+ ///
+ public Rect GetImageViewPort()
+ {
+ if (!IsImageLoaded || (ViewPortSize.Width == 0 && ViewPortSize.Height == 0)) return Rect.Empty;
+
+ double xOffset = 0;
+ double yOffset = 0;
+ double width = 0;
+ double height = 0;
+
+ switch (SizeMode)
+ {
+ case SizeModes.Normal:
+ if (AutoCenter)
+ {
+ xOffset = (!IsHorizontalBarVisible ? (ViewPortSize.Width - ScaledImageWidth) / 2 : 0);
+ yOffset = (!IsVerticalBarVisible ? (ViewPortSize.Height - ScaledImageHeight) / 2 : 0);
+ }
+
+ width = Math.Min(ScaledImageWidth - Math.Abs(Offset.X), ViewPortSize.Width);
+ height = Math.Min(ScaledImageHeight - Math.Abs(Offset.Y), ViewPortSize.Height);
+ break;
+ case SizeModes.Stretch:
+ width = ViewPortSize.Width;
+ height = ViewPortSize.Height;
+ break;
+ case SizeModes.Fit:
+ var image = Image;
+ double scaleFactor = Math.Min(ViewPortSize.Width / image!.Size.Width, ViewPortSize.Height / image.Size.Height);
+
+ width = Math.Floor(image.Size.Width * scaleFactor);
+ height = Math.Floor(image.Size.Height * scaleFactor);
+
+ if (AutoCenter)
+ {
+ xOffset = (ViewPortSize.Width - width) / 2;
+ yOffset = (ViewPortSize.Height - height) / 2;
+ }
+
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(SizeMode), SizeMode, null);
+ }
+
+ return new(xOffset, yOffset, width, height);
+ }
+ #endregion
+
+ #region Image methods
+ public void LoadImage(string path)
+ {
+ Image = new Bitmap(path);
+ }
+
+ public Bitmap? GetSelectedBitmap()
+ {
+ var image = ImageAsWriteableBitmap;
+ if (image is null || !HaveSelection) return null;
+
+ var selection = SelectionRegionNet;
+ var pixelSize = SelectionPixelSize;
+ using var frameBuffer = image.Lock();
+
+ var newBitmap = new WriteableBitmap(pixelSize, image.Dpi, frameBuffer.Format, AlphaFormat.Unpremul);
+ using var newFrameBuffer = newBitmap.Lock();
+
+ int i = 0;
+
+ unsafe
+ {
+ var inputPixels = (uint*) (void*) frameBuffer.Address;
+ var targetPixels = (uint*) (void*) newFrameBuffer.Address;
+
+ for (int y = selection.Y; y < selection.Bottom; y++)
+ {
+ var thisY = y * frameBuffer.Size.Width;
+ for (int x = selection.X; x < selection.Right; x++)
+ {
+ targetPixels![i++] = inputPixels![thisY + x];
+ }
+ }
+ }
+
+ return newBitmap;
+ }
+ #endregion
+
+}
\ No newline at end of file
diff --git a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
index 7ed8446..5974565 100644
--- a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
+++ b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
@@ -1,7 +1,7 @@
- net5.0
+ net6.0
Tiago Conceição
PTRTECH
Copyright © 2020 PTRTECH
@@ -14,6 +14,7 @@
true
AvaloniaUI Controls
1.0.1
+ enable
@@ -34,7 +35,7 @@
-
+
diff --git a/UVtools.Core/About.cs b/UVtools.Core/About.cs
index 1bc41d5..afde01e 100644
--- a/UVtools.Core/About.cs
+++ b/UVtools.Core/About.cs
@@ -9,24 +9,23 @@
using System;
using System.Reflection;
-namespace UVtools.Core
+namespace UVtools.Core;
+
+public static class About
{
- public static class About
- {
- public const string Software = "UVtools";
- public static string SoftwareWithVersion => $"{Software} v{VersionStr}";
- public const string Author = "Tiago Conceição";
- public const string Company = "PTRTECH";
- public const string License = "GNU Affero General Public License v3.0 (AGPL)";
- public const string LicenseUrl = "https://github.com/sn4k3/UVtools/blob/master/LICENSE";
- public const string Website = "https://github.com/sn4k3/UVtools";
- public const string Donate = "https://paypal.me/SkillTournament";
- public const string Sponsor = "https://github.com/sponsors/sn4k3";
+ public const string Software = "UVtools";
+ public static string SoftwareWithVersion => $"{Software} v{VersionStr}";
+ public const string Author = "Tiago Conceição";
+ public const string Company = "PTRTECH";
+ public const string License = "GNU Affero General Public License v3.0 (AGPL)";
+ public const string LicenseUrl = "https://github.com/sn4k3/UVtools/blob/master/LICENSE";
+ public const string Website = "https://github.com/sn4k3/UVtools";
+ public const string Donate = "https://paypal.me/SkillTournament";
+ public const string Sponsor = "https://github.com/sponsors/sn4k3";
- public const string DemoFile = "UVtools_demo_file.sl1";
+ public const string DemoFile = "UVtools_demo_file.sl1";
- public static Version Version => Assembly.GetExecutingAssembly().GetName().Version;
- public static string VersionStr => Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
- public static string Arch => Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits";
- }
-}
+ public static Version Version => Assembly.GetExecutingAssembly().GetName().Version!;
+ public static string VersionStr => Version.ToString(3);
+ public static string Arch => Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits";
+}
\ No newline at end of file
diff --git a/UVtools.Core/Converters/NullTerminatedConverter.cs b/UVtools.Core/Converters/NullTerminatedConverter.cs
index 077e87f..e456bd6 100644
--- a/UVtools.Core/Converters/NullTerminatedConverter.cs
+++ b/UVtools.Core/Converters/NullTerminatedConverter.cs
@@ -7,18 +7,17 @@
*/
using BinarySerialization;
-namespace UVtools.Core.Converters
-{
- public class NullTerminatedConverter : IValueConverter
- {
- public object Convert(object value, object converterParameter, BinarySerializationContext context)
- {
- return value.ToString()?.TrimEnd(char.MinValue);
- }
+namespace UVtools.Core.Converters;
- public object ConvertBack(object value, object converterParameter, BinarySerializationContext context)
- {
- return value is null ? null : $"{value}{char.MinValue}";
- }
+public class NullTerminatedConverter : IValueConverter
+{
+ public object Convert(object value, object converterParameter, BinarySerializationContext context)
+ {
+ return value.ToString()!.TrimEnd(char.MinValue);
}
-}
+
+ public object ConvertBack(object value, object converterParameter, BinarySerializationContext context)
+ {
+ return $"{value}{char.MinValue}";
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Converters/NullTerminatedLengthConverter.cs b/UVtools.Core/Converters/NullTerminatedLengthConverter.cs
index d097e00..1397e54 100644
--- a/UVtools.Core/Converters/NullTerminatedLengthConverter.cs
+++ b/UVtools.Core/Converters/NullTerminatedLengthConverter.cs
@@ -7,25 +7,24 @@
*/
using BinarySerialization;
-namespace UVtools.Core.Converters
-{
- public class NullTerminatedLengthConverter : IValueConverter
- {
- // Read
- public object Convert(object value, object converterParameter, BinarySerializationContext context)
- {
- //var uintValue = System.Convert.ToUInt32(value);
- //if (uintValue == 0) return 0;
- //return uintValue - 1;
- return value;
- }
+namespace UVtools.Core.Converters;
- // Write
- public object ConvertBack(object value, object converterParameter, BinarySerializationContext context)
- {
- var uintValue = System.Convert.ToUInt32(value);
- if (uintValue == 0) return 0;
- return uintValue + 1;
- }
+public class NullTerminatedLengthConverter : IValueConverter
+{
+ // Read
+ public object Convert(object value, object converterParameter, BinarySerializationContext context)
+ {
+ //var uintValue = System.Convert.ToUInt32(value);
+ //if (uintValue == 0) return 0;
+ //return uintValue - 1;
+ return value;
}
-}
+
+ // Write
+ public object ConvertBack(object value, object converterParameter, BinarySerializationContext context)
+ {
+ var uintValue = System.Convert.ToUInt32(value);
+ if (uintValue == 0) return 0;
+ return uintValue + 1;
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/CoreSettings.cs b/UVtools.Core/CoreSettings.cs
index 1301579..e7b54fb 100644
--- a/UVtools.Core/CoreSettings.cs
+++ b/UVtools.Core/CoreSettings.cs
@@ -6,48 +6,86 @@
* of this license document, but changing it is not allowed.
*/
-using System;
-using System.Threading.Tasks;
using Emgu.CV.Cuda;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading.Tasks;
-namespace UVtools.Core
+namespace UVtools.Core;
+
+public static class CoreSettings
{
- public static class CoreSettings
+ #region Members
+ private static int _maxDegreeOfParallelism = -1;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the maximum number of concurrent tasks enabled by this ParallelOptions instance.
+ /// Less or equal to 0 will set to auto number
+ /// 1 = Single thread
+ /// n = Multi threads
+ ///
+ public static int MaxDegreeOfParallelism
{
- #region Members
- private static int _maxDegreeOfParallelism = -1;
-
- #endregion
-
- #region Properties
-
- ///
- /// Gets or sets the maximum number of concurrent tasks enabled by this ParallelOptions instance.
- /// Less or equal to 0 will set to auto number
- /// 1 = Single thread
- /// n = Multi threads
- ///
- public static int MaxDegreeOfParallelism
- {
- get => _maxDegreeOfParallelism;
- set => _maxDegreeOfParallelism = value > 0 ? Math.Min(value, Environment.ProcessorCount) : -1;
- }
-
- ///
- /// Gets the ParallelOptions with set
- ///
- public static ParallelOptions ParallelOptions => new() {MaxDegreeOfParallelism = _maxDegreeOfParallelism};
-
- ///
- /// Gets or sets if operations run via CUDA when possible
- ///
- public static bool EnableCuda { get; set; }
-
- ///
- /// Gets if we can use cuda on operations
- ///
- public static bool CanUseCuda => EnableCuda && CudaInvoke.HasCuda;
-
- #endregion
+ get => _maxDegreeOfParallelism;
+ set => _maxDegreeOfParallelism = value > 0 ? Math.Min(value, Environment.ProcessorCount) : -1;
}
-}
+
+ ///
+ /// Gets the ParallelOptions with set
+ ///
+ public static ParallelOptions ParallelOptions => new() {MaxDegreeOfParallelism = _maxDegreeOfParallelism};
+
+ ///
+ /// Gets or sets if operations run via CUDA when possible
+ ///
+ public static bool EnableCuda { get; set; }
+
+ ///
+ /// Gets if we can use cuda on operations
+ ///
+ public static bool CanUseCuda => EnableCuda && CudaInvoke.HasCuda;
+
+ ///
+ /// Gets the default folder to save the settings
+ ///
+ public static string DefaultSettingsFolder
+ {
+ get
+ {
+ var folder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ if (string.IsNullOrWhiteSpace(folder)) folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ if (string.IsNullOrWhiteSpace(folder)) folder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
+ if (string.IsNullOrWhiteSpace(folder)) folder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ var path = Path.Combine(folder, About.Software);
+ return path;
+ }
+ }
+
+ ///
+ /// Gets the default folder to save the settings
+ ///
+ public static string DefaultSettingsFolderAndEnsureCreation
+ {
+ get
+ {
+ var path = DefaultSettingsFolder;
+ try
+ {
+ if (!Directory.Exists(path))
+ Directory.CreateDirectory(path);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+ return path;
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/UVtools.Core/EmguCV/EmguContour.cs b/UVtools.Core/EmguCV/EmguContour.cs
index 9b43995..4ecfecb 100644
--- a/UVtools.Core/EmguCV/EmguContour.cs
+++ b/UVtools.Core/EmguCV/EmguContour.cs
@@ -6,256 +6,255 @@
* of this license document, but changing it is not allowed.
*/
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Drawing;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Drawing;
using UVtools.Core.Extensions;
-namespace UVtools.Core.EmguCV
+namespace UVtools.Core.EmguCV;
+
+///
+/// A contour cache for OpenCV
+///
+public class EmguContour : IReadOnlyCollection, IDisposable
{
+ #region Constants
+
+ public const byte HierarchyNextSameLevel = 0;
+ public const byte HierarchyPreviousSameLevel = 1;
+ public const byte HierarchyFirstChild = 2;
+ public const byte HierarchyParent = 3;
+
+ #endregion
+
+ #region Members
+
+ private VectorOfPoint _points = null!;
+ private Rectangle? _bounds;
+ private RotatedRect? _boundsBestFit;
+ private CircleF? _minEnclosingCircle;
+ private bool? _isConvex;
+ private double _area = double.NaN;
+ private double _perimeter = double.NaN;
+ private Moments? _moments;
+ private Point? _centroid;
+
+ #endregion
+
+ #region Properties
+ public int XMin => Bounds.X;
+
+ public int YMin => Bounds.Y;
+
+ public int XMax => Bounds.Right;
+
+ public int YMax => Bounds.Bottom;
+
+ public Rectangle Bounds => _bounds ??= CvInvoke.BoundingRectangle(_points);
+
+ public RotatedRect BoundsBestFit => _boundsBestFit ??= CvInvoke.MinAreaRect(_points);
+
+ public CircleF MinEnclosingCircle => _minEnclosingCircle ??= CvInvoke.MinEnclosingCircle(_points);
+
+ public bool IsConvex => _isConvex ??= CvInvoke.IsContourConvex(_points);
+
///
- /// A contour cache for OpenCV
+ /// Gets the area of the contour
///
- public class EmguContour : IReadOnlyCollection, IDisposable
+ public double Area
{
- #region Constants
-
- public const byte HierarchyNextSameLevel = 0;
- public const byte HierarchyPreviousSameLevel = 1;
- public const byte HierarchyFirstChild = 2;
- public const byte HierarchyParent = 3;
-
- #endregion
-
- #region Members
-
- private VectorOfPoint _points;
- private Rectangle? _bounds;
- private RotatedRect? _boundsBestFit;
- private CircleF? _minEnclosingCircle;
- private bool? _isConvex;
- private double _area = double.NaN;
- private double _perimeter = double.NaN;
- private Moments _moments;
- private Point? _centroid;
-
- #endregion
-
- #region Properties
- public int XMin => Bounds.X;
-
- public int YMin => Bounds.Y;
-
- public int XMax => Bounds.Right;
-
- public int YMax => Bounds.Bottom;
-
- public Rectangle Bounds => _bounds ??= CvInvoke.BoundingRectangle(_points);
-
- public RotatedRect BoundsBestFit => _boundsBestFit ??= CvInvoke.MinAreaRect(_points);
-
- public CircleF MinEnclosingCircle => _minEnclosingCircle ??= CvInvoke.MinEnclosingCircle(_points);
-
- public bool IsConvex => _isConvex ??= CvInvoke.IsContourConvex(_points);
-
- ///
- /// Gets the area of the contour
- ///
- public double Area
+ get
{
- get
+ if (double.IsNaN(_area))
{
- if (double.IsNaN(_area))
- {
- _area = CvInvoke.ContourArea(_points);
- }
+ _area = CvInvoke.ContourArea(_points);
+ }
- return _area;
- }
+ return _area;
}
-
- ///
- /// Gets the perimeter of the contours
- ///
- public double Perimeter
- {
- get
- {
- if (double.IsNaN(_perimeter))
- {
- _perimeter = CvInvoke.ArcLength(_points, true);
- }
- return _perimeter;
- }
- }
-
- public Moments Moments => _moments ??= CvInvoke.Moments(_points);
-
- ///
- /// Gets the centroid of the contour
- ///
- public Point Centroid => _centroid ??= Moments.M00 == 0 ? new Point(-1,-1) :
- new Point(
- (int)Math.Round(_moments.M10 / _moments.M00),
- (int)Math.Round(_moments.M01 / _moments.M00));
-
- ///
- /// Gets or sets the contour
- ///
- public VectorOfPoint Points
- {
- get => _points;
- set
- {
- Dispose();
- _points = value ?? throw new ArgumentNullException(nameof(Points));
- }
- }
-
-
- ///
- /// Gets if this contour have any point
- ///
- public bool IsEmpty => _points.Size == 0;
- #endregion
-
- #region Constructor
- public EmguContour(VectorOfPoint points) : this(points.ToArray())
- { }
-
- public EmguContour(Point[] points)
- {
- Points = new VectorOfPoint(points);
- }
- #endregion
-
- #region Methods
-
- ///
- /// Checks if a given is inside the contour rectangle bounds
- ///
- ///
- ///
- public bool IsInsideBounds(Point point) => Bounds.Contains(point);
-
- ///
- /// Gets if a given is inside the contour
- ///
- ///
- ///
- public bool IsInside(Point point)
- {
- if (!IsInsideBounds(point)) return false;
- return CvInvoke.PointPolygonTest(_points, point, false) >= 0;
- }
-
- public double MeasureDist(Point point)
- {
- if (!IsInsideBounds(point)) return -1;
- return CvInvoke.PointPolygonTest(_points, point, true);
- }
-
- public IOutputArray ContourApproximation(double epsilon = 0.1)
- {
- var mat = new Mat();
- CvInvoke.ApproxPolyDP(_points, mat, epsilon*Perimeter, true);
- return mat;
- }
-
- /*
- ///
- /// Calculate the X/Y min/max boundary
- ///
- private void CalculateMinMax()
- {
- Bounds = Rectangle.Empty;
-
- if (_contourPoints.Length == 0)
- {
- _xMin = -1;
- _yMin = -1;
- _xMax = -1;
- _yMax = -1;
- return;
- }
-
- _xMin = int.MaxValue;
- _yMin = int.MaxValue;
- _xMax = int.MinValue;
- _yMax = int.MinValue;
-
- for (int i = 0; i < _contourPoints.Length; i++)
- {
- _xMin = Math.Min(_xMin, _contourPoints[i].X);
- _yMin = Math.Min(_yMin, _contourPoints[i].Y);
-
- _xMax = Math.Max(_xMax, _contourPoints[i].X);
- _yMax = Math.Max(_yMax, _contourPoints[i].Y);
- }
-
- Bounds = new Rectangle(_xMin, _yMin, _xMax - _xMin, _yMax - _yMin);
- }
- */
-
- public void FitCircle(Mat src, MCvScalar color, int thickness = 1, LineType lineType = LineType.EightConnected, int shift = 0)
- {
- CvInvoke.Circle(src,
- MinEnclosingCircle.Center.ToPoint(),
- (int) Math.Round(MinEnclosingCircle.Radius),
- color,
- thickness,
- lineType,
- shift);
- }
-
- /*public void FitEllipse(Mat src, MCvScalar color, int thickness = 1, LineType lineType = LineType.EightConnected, int shift = 0)
- {
- var ellipse = CvInvoke.FitEllipse(_points);
- CvInvoke.Ellipse(src, ellipse.Center.ToPoint(), ellipse.Size.ToSize(), ellipse.Angle, 0, 0);
- }*/
- #endregion
-
- #region Static methods
- public static Point GetCentroid(VectorOfPoint points)
- {
- if (points is null || points.Length == 0) return new Point(-1, -1);
- using var moments = CvInvoke.Moments(points);
- return moments.M00 == 0 ? new Point(-1, -1) :
- new Point(
- (int)Math.Round(moments.M10 / moments.M00),
- (int)Math.Round(moments.M01 / moments.M00));
- }
- #endregion
-
- #region Implementations
-
- public IEnumerator GetEnumerator()
- {
- return (IEnumerator) _points.ToArray().GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- public int Count => _points.Size;
-
- public Point this[int index] => _points[index];
- public Point this[uint index] => _points[(int) index];
- public Point this[long index] => _points[(int) index];
- public Point this[ulong index] => _points[(int) index];
-
- public void Dispose()
- {
- _points?.Dispose();
- _moments?.Dispose();
- }
- #endregion
}
-}
+
+ ///
+ /// Gets the perimeter of the contours
+ ///
+ public double Perimeter
+ {
+ get
+ {
+ if (double.IsNaN(_perimeter))
+ {
+ _perimeter = CvInvoke.ArcLength(_points, true);
+ }
+ return _perimeter;
+ }
+ }
+
+ public Moments Moments => _moments ??= CvInvoke.Moments(_points);
+
+ ///
+ /// Gets the centroid of the contour
+ ///
+ public Point Centroid => _centroid ??= Moments.M00 == 0 ? new Point(-1,-1) :
+ new Point(
+ (int)Math.Round(Moments.M10 / Moments.M00),
+ (int)Math.Round(Moments.M01 / Moments.M00));
+
+ ///
+ /// Gets or sets the contour
+ ///
+ public VectorOfPoint Points
+ {
+ get => _points;
+ set
+ {
+ Dispose();
+ _points = value ?? throw new ArgumentNullException(nameof(Points));
+ }
+ }
+
+
+ ///
+ /// Gets if this contour have any point
+ ///
+ public bool IsEmpty => _points.Size == 0;
+ #endregion
+
+ #region Constructor
+ public EmguContour(VectorOfPoint points) : this(points.ToArray())
+ { }
+
+ public EmguContour(Point[] points)
+ {
+ Points = new VectorOfPoint(points);
+ }
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Checks if a given is inside the contour rectangle bounds
+ ///
+ ///
+ ///
+ public bool IsInsideBounds(Point point) => Bounds.Contains(point);
+
+ ///
+ /// Gets if a given is inside the contour
+ ///
+ ///
+ ///
+ public bool IsInside(Point point)
+ {
+ if (!IsInsideBounds(point)) return false;
+ return CvInvoke.PointPolygonTest(_points, point, false) >= 0;
+ }
+
+ public double MeasureDist(Point point)
+ {
+ if (!IsInsideBounds(point)) return -1;
+ return CvInvoke.PointPolygonTest(_points, point, true);
+ }
+
+ public IOutputArray ContourApproximation(double epsilon = 0.1)
+ {
+ var mat = new Mat();
+ CvInvoke.ApproxPolyDP(_points, mat, epsilon*Perimeter, true);
+ return mat;
+ }
+
+ /*
+ ///
+ /// Calculate the X/Y min/max boundary
+ ///
+ private void CalculateMinMax()
+ {
+ Bounds = Rectangle.Empty;
+
+ if (_contourPoints.Length == 0)
+ {
+ _xMin = -1;
+ _yMin = -1;
+ _xMax = -1;
+ _yMax = -1;
+ return;
+ }
+
+ _xMin = int.MaxValue;
+ _yMin = int.MaxValue;
+ _xMax = int.MinValue;
+ _yMax = int.MinValue;
+
+ for (int i = 0; i < _contourPoints.Length; i++)
+ {
+ _xMin = Math.Min(_xMin, _contourPoints[i].X);
+ _yMin = Math.Min(_yMin, _contourPoints[i].Y);
+
+ _xMax = Math.Max(_xMax, _contourPoints[i].X);
+ _yMax = Math.Max(_yMax, _contourPoints[i].Y);
+ }
+
+ Bounds = new Rectangle(_xMin, _yMin, _xMax - _xMin, _yMax - _yMin);
+ }
+ */
+
+ public void FitCircle(Mat src, MCvScalar color, int thickness = 1, LineType lineType = LineType.EightConnected, int shift = 0)
+ {
+ CvInvoke.Circle(src,
+ MinEnclosingCircle.Center.ToPoint(),
+ (int) Math.Round(MinEnclosingCircle.Radius),
+ color,
+ thickness,
+ lineType,
+ shift);
+ }
+
+ /*public void FitEllipse(Mat src, MCvScalar color, int thickness = 1, LineType lineType = LineType.EightConnected, int shift = 0)
+ {
+ var ellipse = CvInvoke.FitEllipse(_points);
+ CvInvoke.Ellipse(src, ellipse.Center.ToPoint(), ellipse.Size.ToSize(), ellipse.Angle, 0, 0);
+ }*/
+ #endregion
+
+ #region Static methods
+ public static Point GetCentroid(VectorOfPoint points)
+ {
+ if (points is null || points.Length == 0) return new Point(-1, -1);
+ using var moments = CvInvoke.Moments(points);
+ return moments.M00 == 0 ? new Point(-1, -1) :
+ new Point(
+ (int)Math.Round(moments.M10 / moments.M00),
+ (int)Math.Round(moments.M01 / moments.M00));
+ }
+ #endregion
+
+ #region Implementations
+
+ public IEnumerator GetEnumerator()
+ {
+ return (IEnumerator) _points.ToArray().GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public int Count => _points.Size;
+
+ public Point this[int index] => _points[index];
+ public Point this[uint index] => _points[(int) index];
+ public Point this[long index] => _points[(int) index];
+ public Point this[ulong index] => _points[(int) index];
+
+ public void Dispose()
+ {
+ _points?.Dispose();
+ _moments?.Dispose();
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/UVtools.Core/EmguCV/EmguContours.cs b/UVtools.Core/EmguCV/EmguContours.cs
index 3c15b56..958cfd8 100644
--- a/UVtools.Core/EmguCV/EmguContours.cs
+++ b/UVtools.Core/EmguCV/EmguContours.cs
@@ -6,246 +6,245 @@
* of this license document, but changing it is not allowed.
*/
+using Emgu.CV;
+using Emgu.CV.Util;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
-using Emgu.CV;
-using Emgu.CV.Util;
using UVtools.Core.Extensions;
-namespace UVtools.Core.EmguCV
+namespace UVtools.Core.EmguCV;
+
+///
+/// Utility methods for contour handling.
+/// Use only with Tree type
+///
+public static class EmguContours
{
///
- /// Utility methods for contour handling.
- /// Use only with Tree type
+ /// Gets contours inside a point
///
- public static class EmguContours
+ ///
+ ///
+ ///
+ /// If true it will include all limiting area, otherwise only outer contour will be returned
+ ///
+ public static VectorOfVectorOfPoint GetContoursInside(VectorOfVectorOfPoint contours, int[,] hierarchy, Point location, bool includeLimitingArea = true)
{
- ///
- /// Gets contours inside a point
- ///
- ///
- ///
- ///
- /// If true it will include all limiting area, otherwise only outer contour will be returned
- ///
- public static VectorOfVectorOfPoint GetContoursInside(VectorOfVectorOfPoint contours, int[,] hierarchy, Point location, bool includeLimitingArea = true)
+ var vector = new VectorOfVectorOfPoint();
+ var vectorSize = contours.Size;
+ for (var i = vectorSize - 1; i >= 0; i--)
{
- var vector = new VectorOfVectorOfPoint();
- var vectorSize = contours.Size;
- for (var i = vectorSize - 1; i >= 0; i--)
+ if (CvInvoke.PointPolygonTest(contours[i], location, false) < 0) continue;
+ vector.Push(contours[i]);
+ if (!includeLimitingArea) break;
+ for (int n = i + 1; n < vectorSize; n++)
{
- if (CvInvoke.PointPolygonTest(contours[i], location, false) < 0) continue;
- vector.Push(contours[i]);
- if (!includeLimitingArea) break;
- for (int n = i + 1; n < vectorSize; n++)
- {
- if (hierarchy[n, EmguContour.HierarchyParent] != i) continue;
- vector.Push(contours[n]);
- }
- break;
+ if (hierarchy[n, EmguContour.HierarchyParent] != i) continue;
+ vector.Push(contours[n]);
}
-
- return vector;
+ break;
}
- ///
- /// Gets a contour given a location.
- ///
- ///
- ///
- /// Contour index, -1 if not exists
- /// null if not exists
- public static VectorOfPoint GetContourInside(VectorOfVectorOfPoint contours, Point location, out int index)
- {
- index = -1;
- var vectorSize = contours.Size;
- for (int i = vectorSize - 1; i >= 0; i--)
- {
- if (CvInvoke.PointPolygonTest(contours[i], location, false) < 0) continue;
- index = i;
- return contours[i];
- }
-
- return null;
- }
-
- ///
- /// Gets only the outer most external contours
- /// Only compatible with Tree type of contour detection
- ///
- ///
- ///
- ///
- public static VectorOfVectorOfPoint GetExternalContours(VectorOfVectorOfPoint contours, int[,] hierarchy)
- {
- var result = new VectorOfVectorOfPoint();
- var vectorSize = contours.Size;
- for (var i = 0; i < vectorSize; i++)
- {
- if (hierarchy[i, EmguContour.HierarchyParent] != -1) continue;
- result.Push(contours[i]);
- }
-
- return result;
- }
-
- ///
- /// Gets contours inside contours that are black pixels
- ///
- ///
- ///
- ///
- public static VectorOfVectorOfPoint GetNegativeContours(VectorOfVectorOfPoint contours, int[,] hierarchy)
- {
- var result = new VectorOfVectorOfPoint();
- var vectorSize = contours.Size;
- for (var i = 0; i < vectorSize; i++)
- {
- if (hierarchy[i, EmguContour.HierarchyParent] == -1) continue;
- result.Push(contours[i]);
- }
-
- return result;
- }
-
- ///
- /// Gets contours that are positive and negative pixels and group them by areas
- /// Only compatible with Tree type of contour detection
- ///
- ///
- public static List[] GetContoursInGroups(VectorOfVectorOfPoint contours, int[,] hierarchy)
- {
- return new []{GetPositiveContoursInGroups(contours, hierarchy), GetNegativeContoursInGroups(contours, hierarchy)};
- }
-
- ///
- /// Gets contours that are positive pixels and group them by areas
- /// Only compatible with Tree type of contour detection
- ///
- ///
- public static List GetPositiveContoursInGroups(VectorOfVectorOfPoint contours, int[,] hierarchy)
- {
- var result = new List();
- var vectorSize = contours.Size;
- var processedContours = new bool[vectorSize];
- for (int i = 0; i < vectorSize; i++)
- {
- if (processedContours[i]) continue;
- processedContours[i] = true;
- var index = result.Count;
- result.Add(new VectorOfVectorOfPoint(contours[i]));
- for (int n = i + 1; n < vectorSize; n++)
- {
- if (processedContours[n] || hierarchy[n, EmguContour.HierarchyParent] != i) continue;
- processedContours[n] = true;
- result[index].Push(contours[n]);
- }
- }
-
- return result;
- }
-
- ///
- /// Gets contours inside contours that are black pixels and group them by areas
- /// Only compatible with Tree type of contour detection
- ///
- ///
- public static List GetNegativeContoursInGroups(VectorOfVectorOfPoint contours, int[,] hierarchy)
- {
- var result = new List();
- var vectorSize = contours.Size;
- var processedContours = new bool[vectorSize];
- for (int i = 0; i < vectorSize; i++)
- {
- if (processedContours[i]) continue;
- processedContours[i] = true;
- if (hierarchy[i, EmguContour.HierarchyParent] == -1) continue;
- var index = result.Count;
- result.Add(new VectorOfVectorOfPoint(contours[i]));
- for (int n = i + 1; n < vectorSize; n++)
- {
- if (processedContours[n] || hierarchy[n, EmguContour.HierarchyParent] != i) continue;
- processedContours[n] = true;
- result[index].Push(contours[n]);
- }
- }
-
- return result;
- }
-
- ///
- /// Gets contour real area for a limited area
- ///
- ///
- ///
- public static double GetContourArea(VectorOfVectorOfPoint contours)
- {
- var vectorSize = contours.Size;
- if (vectorSize == 0) return 0;
-
- double result = CvInvoke.ContourArea(contours[0]);
- for (var i = 1; i < vectorSize; i++)
- {
- result -= CvInvoke.ContourArea(contours[i]);
- }
- return result;
- }
-
- ///
- /// Gets contours real area for a group of contours
- ///
- /// Grouped contours
- /// True to run in parallel
- /// Array with same size with contours area
- public static double[] GetContoursArea(List contours, bool useParallel = false)
- {
- var result = new double[contours.Count];
-
- if (useParallel)
- {
- Parallel.For(0, contours.Count, CoreSettings.ParallelOptions, i =>
- {
- result[i] = GetContourArea(contours[i]);
- });
- }
- else
- {
- for (var i = 0; i < contours.Count; i++)
- {
- result[i] = GetContourArea(contours[i]);
- }
- }
-
- return result;
- }
-
- ///
- /// Checks if two contours intersects
- ///
- /// Contour 1
- /// Contour 2
- ///
- public static bool ContoursIntersect(VectorOfVectorOfPoint contour1, VectorOfVectorOfPoint contour2)
- {
- var contour1Rect = CvInvoke.BoundingRectangle(contour1[0]);
- var contour2Rect = CvInvoke.BoundingRectangle(contour2[0]);
-
- /* early exit if the bounding rectangles don't intersect */
- if (!contour1Rect.IntersectsWith(contour2Rect)) return false;
- var totalRect = Rectangle.Union(contour1Rect, contour2Rect);
-
- using var contour1Mat = EmguExtensions.InitMat(totalRect.Size);
- using var contour2Mat = EmguExtensions.InitMat(totalRect.Size);
-
- var inverseOffset = new Point(-totalRect.X, -totalRect.Y);
- CvInvoke.DrawContours(contour1Mat, contour1, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
- CvInvoke.DrawContours(contour2Mat, contour2, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
-
- CvInvoke.BitwiseAnd(contour1Mat, contour2Mat, contour1Mat);
-
- //return !contour1Mat.IsZeroed();
- return CvInvoke.CountNonZero(contour1Mat) > 0;
- }
+ return vector;
}
-}
+
+ ///
+ /// Gets a contour given a location.
+ ///
+ ///
+ ///
+ /// Contour index, -1 if not exists
+ /// null if not exists
+ public static VectorOfPoint? GetContourInside(VectorOfVectorOfPoint contours, Point location, out int index)
+ {
+ index = -1;
+ var vectorSize = contours.Size;
+ for (int i = vectorSize - 1; i >= 0; i--)
+ {
+ if (CvInvoke.PointPolygonTest(contours[i], location, false) < 0) continue;
+ index = i;
+ return contours[i];
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets only the outer most external contours
+ /// Only compatible with Tree type of contour detection
+ ///
+ ///
+ ///
+ ///
+ public static VectorOfVectorOfPoint GetExternalContours(VectorOfVectorOfPoint contours, int[,] hierarchy)
+ {
+ var result = new VectorOfVectorOfPoint();
+ var vectorSize = contours.Size;
+ for (var i = 0; i < vectorSize; i++)
+ {
+ if (hierarchy[i, EmguContour.HierarchyParent] != -1) continue;
+ result.Push(contours[i]);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Gets contours inside contours that are black pixels
+ ///
+ ///
+ ///
+ ///
+ public static VectorOfVectorOfPoint GetNegativeContours(VectorOfVectorOfPoint contours, int[,] hierarchy)
+ {
+ var result = new VectorOfVectorOfPoint();
+ var vectorSize = contours.Size;
+ for (var i = 0; i < vectorSize; i++)
+ {
+ if (hierarchy[i, EmguContour.HierarchyParent] == -1) continue;
+ result.Push(contours[i]);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Gets contours that are positive and negative pixels and group them by areas
+ /// Only compatible with Tree type of contour detection
+ ///
+ ///
+ public static List[] GetContoursInGroups(VectorOfVectorOfPoint contours, int[,] hierarchy)
+ {
+ return new []{GetPositiveContoursInGroups(contours, hierarchy), GetNegativeContoursInGroups(contours, hierarchy)};
+ }
+
+ ///
+ /// Gets contours that are positive pixels and group them by areas
+ /// Only compatible with Tree type of contour detection
+ ///
+ ///
+ public static List GetPositiveContoursInGroups(VectorOfVectorOfPoint contours, int[,] hierarchy)
+ {
+ var result = new List();
+ var vectorSize = contours.Size;
+ var processedContours = new bool[vectorSize];
+ for (int i = 0; i < vectorSize; i++)
+ {
+ if (processedContours[i]) continue;
+ processedContours[i] = true;
+ var index = result.Count;
+ result.Add(new VectorOfVectorOfPoint(contours[i]));
+ for (int n = i + 1; n < vectorSize; n++)
+ {
+ if (processedContours[n] || hierarchy[n, EmguContour.HierarchyParent] != i) continue;
+ processedContours[n] = true;
+ result[index].Push(contours[n]);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Gets contours inside contours that are black pixels and group them by areas
+ /// Only compatible with Tree type of contour detection
+ ///
+ ///
+ public static List GetNegativeContoursInGroups(VectorOfVectorOfPoint contours, int[,] hierarchy)
+ {
+ var result = new List();
+ var vectorSize = contours.Size;
+ var processedContours = new bool[vectorSize];
+ for (int i = 0; i < vectorSize; i++)
+ {
+ if (processedContours[i]) continue;
+ processedContours[i] = true;
+ if (hierarchy[i, EmguContour.HierarchyParent] == -1) continue;
+ var index = result.Count;
+ result.Add(new VectorOfVectorOfPoint(contours[i]));
+ for (int n = i + 1; n < vectorSize; n++)
+ {
+ if (processedContours[n] || hierarchy[n, EmguContour.HierarchyParent] != i) continue;
+ processedContours[n] = true;
+ result[index].Push(contours[n]);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Gets contour real area for a limited area
+ ///
+ ///
+ ///
+ public static double GetContourArea(VectorOfVectorOfPoint contours)
+ {
+ var vectorSize = contours.Size;
+ if (vectorSize == 0) return 0;
+
+ double result = CvInvoke.ContourArea(contours[0]);
+ for (var i = 1; i < vectorSize; i++)
+ {
+ result -= CvInvoke.ContourArea(contours[i]);
+ }
+ return result;
+ }
+
+ ///
+ /// Gets contours real area for a group of contours
+ ///
+ /// Grouped contours
+ /// True to run in parallel
+ /// Array with same size with contours area
+ public static double[] GetContoursArea(List contours, bool useParallel = false)
+ {
+ var result = new double[contours.Count];
+
+ if (useParallel)
+ {
+ Parallel.For(0, contours.Count, CoreSettings.ParallelOptions, i =>
+ {
+ result[i] = GetContourArea(contours[i]);
+ });
+ }
+ else
+ {
+ for (var i = 0; i < contours.Count; i++)
+ {
+ result[i] = GetContourArea(contours[i]);
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Checks if two contours intersects
+ ///
+ /// Contour 1
+ /// Contour 2
+ ///
+ public static bool ContoursIntersect(VectorOfVectorOfPoint contour1, VectorOfVectorOfPoint contour2)
+ {
+ var contour1Rect = CvInvoke.BoundingRectangle(contour1[0]);
+ var contour2Rect = CvInvoke.BoundingRectangle(contour2[0]);
+
+ /* early exit if the bounding rectangles don't intersect */
+ if (!contour1Rect.IntersectsWith(contour2Rect)) return false;
+ var totalRect = Rectangle.Union(contour1Rect, contour2Rect);
+
+ using var contour1Mat = EmguExtensions.InitMat(totalRect.Size);
+ using var contour2Mat = EmguExtensions.InitMat(totalRect.Size);
+
+ var inverseOffset = new Point(-totalRect.X, -totalRect.Y);
+ CvInvoke.DrawContours(contour1Mat, contour1, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
+ CvInvoke.DrawContours(contour2Mat, contour2, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
+
+ CvInvoke.BitwiseAnd(contour1Mat, contour2Mat, contour1Mat);
+
+ //return !contour1Mat.IsZeroed();
+ return CvInvoke.CountNonZero(contour1Mat) > 0;
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Enumerations.cs b/UVtools.Core/Enumerations.cs
index 98be0a5..c7b9a96 100644
--- a/UVtools.Core/Enumerations.cs
+++ b/UVtools.Core/Enumerations.cs
@@ -6,93 +6,92 @@
* of this license document, but changing it is not allowed.
*/
+using Emgu.CV.CvEnum;
using System;
using System.ComponentModel;
-using Emgu.CV.CvEnum;
-namespace UVtools.Core
+namespace UVtools.Core;
+
+public class Enumerations
{
- public class Enumerations
+ public enum LayerRangeSelection : byte
{
- public enum LayerRangeSelection : byte
- {
- None,
- All,
- Current,
- Bottom,
- Normal,
- First,
- Last
- }
-
- public enum FlipDirection : byte
- {
- None,
- Horizontally,
- Vertically,
- Both,
- }
-
- public enum RotateDirection : sbyte
- {
- [Description("None")]
- None = -1,
- /// Rotate 90 degrees clockwise (0)
- [Description("Rotate 90º CW")]
- Rotate90Clockwise = 0,
- /// Rotate 180 degrees clockwise (1)
- [Description("Rotate 180º")]
- Rotate180 = 1,
- /// Rotate 270 degrees clockwise (2)
- [Description("Rotate 90º CCW")]
- Rotate90CounterClockwise = 2,
- }
-
- public enum Anchor : byte
- {
- TopLeft, TopCenter, TopRight,
- MiddleLeft, MiddleCenter, MiddleRight,
- BottomLeft, BottomCenter, BottomRight,
- None
- }
-
- public enum LightOffDelaySetMode : byte
- {
- [Description("Set the light-off with an extra delay")]
- UpdateWithExtraDelay,
-
- [Description("Set the light-off without an extra delay")]
- UpdateWithoutExtraDelay,
-
- [Description("Set the light-off to zero")]
- SetToZero,
-
- [Description("Disabled")]
- NoAction
- }
-
- public static FlipType ToOpenCVFlipType(FlipDirection flip)
- {
- return flip switch
- {
- FlipDirection.None => throw new NotSupportedException($"Flip type: {flip} is not supported by OpenCV."),
- FlipDirection.Horizontally => FlipType.Horizontal,
- FlipDirection.Vertically => FlipType.Vertical,
- FlipDirection.Both => FlipType.Both,
- _ => throw new ArgumentOutOfRangeException(nameof(flip), flip, null)
- };
- }
-
- public static RotateFlags ToOpenCVRotateFlags(RotateDirection rotate)
- {
- return rotate switch
- {
- RotateDirection.None => throw new NotSupportedException($"Rotate direction: {rotate} is not supported by OpenCV."),
- RotateDirection.Rotate90Clockwise => RotateFlags.Rotate90Clockwise,
- RotateDirection.Rotate90CounterClockwise => RotateFlags.Rotate90CounterClockwise,
- RotateDirection.Rotate180 => RotateFlags.Rotate180,
- _ => throw new ArgumentOutOfRangeException(nameof(rotate), rotate, null)
- };
- }
+ None,
+ All,
+ Current,
+ Bottom,
+ Normal,
+ First,
+ Last
}
-}
+
+ public enum FlipDirection : byte
+ {
+ None,
+ Horizontally,
+ Vertically,
+ Both,
+ }
+
+ public enum RotateDirection : sbyte
+ {
+ [Description("None")]
+ None = -1,
+ /// Rotate 90 degrees clockwise (0)
+ [Description("Rotate 90º CW")]
+ Rotate90Clockwise = 0,
+ /// Rotate 180 degrees clockwise (1)
+ [Description("Rotate 180º")]
+ Rotate180 = 1,
+ /// Rotate 270 degrees clockwise (2)
+ [Description("Rotate 90º CCW")]
+ Rotate90CounterClockwise = 2,
+ }
+
+ public enum Anchor : byte
+ {
+ TopLeft, TopCenter, TopRight,
+ MiddleLeft, MiddleCenter, MiddleRight,
+ BottomLeft, BottomCenter, BottomRight,
+ None
+ }
+
+ public enum LightOffDelaySetMode : byte
+ {
+ [Description("Set the light-off with an extra delay")]
+ UpdateWithExtraDelay,
+
+ [Description("Set the light-off without an extra delay")]
+ UpdateWithoutExtraDelay,
+
+ [Description("Set the light-off to zero")]
+ SetToZero,
+
+ [Description("Disabled")]
+ NoAction
+ }
+
+ public static FlipType ToOpenCVFlipType(FlipDirection flip)
+ {
+ return flip switch
+ {
+ FlipDirection.None => throw new NotSupportedException($"Flip type: {flip} is not supported by OpenCV."),
+ FlipDirection.Horizontally => FlipType.Horizontal,
+ FlipDirection.Vertically => FlipType.Vertical,
+ FlipDirection.Both => FlipType.Both,
+ _ => throw new ArgumentOutOfRangeException(nameof(flip), flip, null)
+ };
+ }
+
+ public static RotateFlags ToOpenCVRotateFlags(RotateDirection rotate)
+ {
+ return rotate switch
+ {
+ RotateDirection.None => throw new NotSupportedException($"Rotate direction: {rotate} is not supported by OpenCV."),
+ RotateDirection.Rotate90Clockwise => RotateFlags.Rotate90Clockwise,
+ RotateDirection.Rotate90CounterClockwise => RotateFlags.Rotate90CounterClockwise,
+ RotateDirection.Rotate180 => RotateFlags.Rotate180,
+ _ => throw new ArgumentOutOfRangeException(nameof(rotate), rotate, null)
+ };
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/BitExtensions.cs b/UVtools.Core/Extensions/BitExtensions.cs
index 8efe29c..fdbfbb4 100644
--- a/UVtools.Core/Extensions/BitExtensions.cs
+++ b/UVtools.Core/Extensions/BitExtensions.cs
@@ -5,154 +5,153 @@
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class BitExtensions
{
- public static class BitExtensions
+ public static ushort ToUShortLittleEndian(byte byte1, byte byte2) => (ushort)(byte1 + (byte2 << 8));
+ public static ushort ToUShortBigEndian(byte byte1, byte byte2) => (ushort)((byte1 << 8) + byte2);
+
+ public static ushort ToUShortLittleEndian(byte[] buffer, int offset = 0)
+ => (ushort)(buffer[offset] + (buffer[offset+1] << 8));
+ public static ushort ToUShortBigEndian(byte[] buffer, int offset = 0)
+ => (ushort)((buffer[offset] << 8) + buffer[offset+1]);
+
+ public static uint ToUIntLittleEndian(byte byte1, byte byte2, byte byte3, byte byte4)
+ => (uint)(byte1 + (byte2 << 8) + (byte3 << 16) + (byte4 << 24));
+ public static uint ToUIntBigEndian(byte byte1, byte byte2, byte byte3, byte byte4)
+ => (uint)((byte1 << 24) + (byte2 << 16) + (byte3 << 8) + byte4);
+
+ public static uint ToUIntLittleEndian(byte[] buffer, int offset = 0)
+ => (uint)(buffer[offset] + (buffer[offset + 1] << 8) + (buffer[offset + 2] << 16) + (buffer[offset + 3] << 24));
+ public static uint ToUIntBigEndian(byte[] buffer, int offset = 0)
+ => (uint)((buffer[offset] << 24) + (buffer[offset+1] << 16) + (buffer[offset+2] << 8) + buffer[offset+3]);
+
+ public static byte[] ToBytesLittleEndian(ushort value)
{
- public static ushort ToUShortLittleEndian(byte byte1, byte byte2) => (ushort)(byte1 + (byte2 << 8));
- public static ushort ToUShortBigEndian(byte byte1, byte byte2) => (ushort)((byte1 << 8) + byte2);
-
- public static ushort ToUShortLittleEndian(byte[] buffer, int offset = 0)
- => (ushort)(buffer[offset] + (buffer[offset+1] << 8));
- public static ushort ToUShortBigEndian(byte[] buffer, int offset = 0)
- => (ushort)((buffer[offset] << 8) + buffer[offset+1]);
-
- public static uint ToUIntLittleEndian(byte byte1, byte byte2, byte byte3, byte byte4)
- => (uint)(byte1 + (byte2 << 8) + (byte3 << 16) + (byte4 << 24));
- public static uint ToUIntBigEndian(byte byte1, byte byte2, byte byte3, byte byte4)
- => (uint)((byte1 << 24) + (byte2 << 16) + (byte3 << 8) + byte4);
-
- public static uint ToUIntLittleEndian(byte[] buffer, int offset = 0)
- => (uint)(buffer[offset] + (buffer[offset + 1] << 8) + (buffer[offset + 2] << 16) + (buffer[offset + 3] << 24));
- public static uint ToUIntBigEndian(byte[] buffer, int offset = 0)
- => (uint)((buffer[offset] << 24) + (buffer[offset+1] << 16) + (buffer[offset+2] << 8) + buffer[offset+3]);
-
- public static byte[] ToBytesLittleEndian(ushort value)
- {
- var bytes = new byte[2];
- ToBytesLittleEndian(value, bytes);
- return bytes;
- }
-
- public static void ToBytesLittleEndian(ushort value, byte[] buffer, uint offset = 0)
- {
- buffer[offset] = (byte)value;
- buffer[offset + 1] = (byte)(value >> 8);
- }
-
- public static byte[] ToBytesBigEndian(ushort value)
- {
- var bytes = new byte[2];
- ToBytesBigEndian(value, bytes);
- return bytes;
- }
-
- public static void ToBytesBigEndian(ushort value, byte[] buffer, uint offset = 0)
- {
- buffer[offset] = (byte)(value >> 8);
- buffer[offset + 1] = (byte)value;
- }
-
- public static byte[] ToBytesLittleEndian(uint value)
- {
- var bytes = new byte[4];
- ToBytesLittleEndian(value, bytes);
- return bytes;
- }
-
- public static void ToBytesLittleEndian(uint value, byte[] buffer, uint offset = 0)
- {
- buffer[offset] = (byte)value;
- buffer[offset + 1] = (byte)(value >> 8);
- buffer[offset + 2] = (byte)(value >> 16);
- buffer[offset + 3] = (byte)(value >> 24);
- }
-
- public static byte[] ToBytesBigEndian(uint value)
- {
- var bytes = new byte[4];
- ToBytesBigEndian(value, bytes);
- return bytes;
- }
-
- public static void ToBytesBigEndian(uint value, byte[] buffer, uint offset = 0)
- {
- buffer[offset] = (byte)(value >> 24);
- buffer[offset + 1] = (byte)(value >> 16);
- buffer[offset + 2] = (byte)(value >> 8);
- buffer[offset + 3] = (byte)value;
- }
-
- public static byte[] ToBytesLittleEndian(int value)
- {
- var bytes = new byte[4];
- ToBytesLittleEndian(value, bytes);
- return bytes;
- }
-
- public static void ToBytesLittleEndian(int value, byte[] buffer, uint offset = 0)
- {
- buffer[offset] = (byte)value;
- buffer[offset + 1] = (byte)(value >> 8);
- buffer[offset + 2] = (byte)(value >> 16);
- buffer[offset + 3] = (byte)(value >> 24);
- }
-
- public static byte[] ToBytesBigEndian(int value)
- {
- var bytes = new byte[4];
- ToBytesBigEndian(value, bytes);
- return bytes;
- }
-
- public static void ToBytesBigEndian(int value, byte[] buffer, uint offset = 0)
- {
- buffer[offset] = (byte)(value >> 24);
- buffer[offset + 1] = (byte)(value >> 16);
- buffer[offset + 2] = (byte)(value >> 8);
- buffer[offset + 3] = (byte)value;
- }
-
-
- public static byte[] ToBytesLittleEndian(ulong value)
- {
- var bytes = new byte[8];
- ToBytesLittleEndian(value, bytes);
- return bytes;
- }
-
- public static void ToBytesLittleEndian(ulong value, byte[] buffer, ulong offset = 0)
- {
- buffer[offset] = (byte)value;
- buffer[offset + 1] = (byte)(value >> 8);
- buffer[offset + 2] = (byte)(value >> 16);
- buffer[offset + 3] = (byte)(value >> 24);
-
- buffer[offset + 4] = (byte)(value >> 32);
- buffer[offset + 5] = (byte)(value >> 40);
- buffer[offset + 6] = (byte)(value >> 48);
- buffer[offset + 7] = (byte)(value >> 56);
- }
-
- public static byte[] ToBytesBigEndian(ulong value)
- {
- var bytes = new byte[8];
- ToBytesBigEndian(value, bytes);
- return bytes;
- }
-
- public static void ToBytesBigEndian(ulong value, byte[] buffer, ulong offset = 0)
- {
- buffer[offset] = (byte)(value >> 56);
- buffer[offset + 1] = (byte)(value >> 48);
- buffer[offset + 2] = (byte)(value >> 40);
- buffer[offset + 3] = (byte)(value >> 32);
- buffer[offset + 4] = (byte)(value >> 24);
- buffer[offset + 5] = (byte)(value >> 16);
- buffer[offset + 6] = (byte)(value >> 8);
- buffer[offset + 7] = (byte)value;
- }
-
-
+ var bytes = new byte[2];
+ ToBytesLittleEndian(value, bytes);
+ return bytes;
}
-}
+
+ public static void ToBytesLittleEndian(ushort value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)value;
+ buffer[offset + 1] = (byte)(value >> 8);
+ }
+
+ public static byte[] ToBytesBigEndian(ushort value)
+ {
+ var bytes = new byte[2];
+ ToBytesBigEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesBigEndian(ushort value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)(value >> 8);
+ buffer[offset + 1] = (byte)value;
+ }
+
+ public static byte[] ToBytesLittleEndian(uint value)
+ {
+ var bytes = new byte[4];
+ ToBytesLittleEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesLittleEndian(uint value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)value;
+ buffer[offset + 1] = (byte)(value >> 8);
+ buffer[offset + 2] = (byte)(value >> 16);
+ buffer[offset + 3] = (byte)(value >> 24);
+ }
+
+ public static byte[] ToBytesBigEndian(uint value)
+ {
+ var bytes = new byte[4];
+ ToBytesBigEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesBigEndian(uint value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)(value >> 24);
+ buffer[offset + 1] = (byte)(value >> 16);
+ buffer[offset + 2] = (byte)(value >> 8);
+ buffer[offset + 3] = (byte)value;
+ }
+
+ public static byte[] ToBytesLittleEndian(int value)
+ {
+ var bytes = new byte[4];
+ ToBytesLittleEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesLittleEndian(int value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)value;
+ buffer[offset + 1] = (byte)(value >> 8);
+ buffer[offset + 2] = (byte)(value >> 16);
+ buffer[offset + 3] = (byte)(value >> 24);
+ }
+
+ public static byte[] ToBytesBigEndian(int value)
+ {
+ var bytes = new byte[4];
+ ToBytesBigEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesBigEndian(int value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)(value >> 24);
+ buffer[offset + 1] = (byte)(value >> 16);
+ buffer[offset + 2] = (byte)(value >> 8);
+ buffer[offset + 3] = (byte)value;
+ }
+
+
+ public static byte[] ToBytesLittleEndian(ulong value)
+ {
+ var bytes = new byte[8];
+ ToBytesLittleEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesLittleEndian(ulong value, byte[] buffer, ulong offset = 0)
+ {
+ buffer[offset] = (byte)value;
+ buffer[offset + 1] = (byte)(value >> 8);
+ buffer[offset + 2] = (byte)(value >> 16);
+ buffer[offset + 3] = (byte)(value >> 24);
+
+ buffer[offset + 4] = (byte)(value >> 32);
+ buffer[offset + 5] = (byte)(value >> 40);
+ buffer[offset + 6] = (byte)(value >> 48);
+ buffer[offset + 7] = (byte)(value >> 56);
+ }
+
+ public static byte[] ToBytesBigEndian(ulong value)
+ {
+ var bytes = new byte[8];
+ ToBytesBigEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesBigEndian(ulong value, byte[] buffer, ulong offset = 0)
+ {
+ buffer[offset] = (byte)(value >> 56);
+ buffer[offset + 1] = (byte)(value >> 48);
+ buffer[offset + 2] = (byte)(value >> 40);
+ buffer[offset + 3] = (byte)(value >> 32);
+ buffer[offset + 4] = (byte)(value >> 24);
+ buffer[offset + 5] = (byte)(value >> 16);
+ buffer[offset + 6] = (byte)(value >> 8);
+ buffer[offset + 7] = (byte)value;
+ }
+
+
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/ClassExtensions.cs b/UVtools.Core/Extensions/ClassExtensions.cs
index 2f21c1b..222c76f 100644
--- a/UVtools.Core/Extensions/ClassExtensions.cs
+++ b/UVtools.Core/Extensions/ClassExtensions.cs
@@ -6,22 +6,21 @@
* of this license document, but changing it is not allowed.
*/
-using Newtonsoft.Json;
+using System.Text.Json;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class ClassExtensions
{
- public static class ClassExtensions
+ public static T? CloneByJsonSerialization(this T classToClone) where T : class
{
- public static T CloneByJsonSerialization(this T classToClone) where T : class
- {
- var clone = JsonConvert.SerializeObject (classToClone);
- return JsonConvert.DeserializeObject(clone);
- }
-
- public static T CloneByXmlSerialization(this T classToClone) where T : class
- {
- var clone = XmlExtensions.SerializeObject(classToClone);
- return XmlExtensions.DeserializeObject(clone);
- }
+ var clone = JsonSerializer.SerializeToUtf8Bytes(classToClone);
+ return JsonSerializer.Deserialize(clone);
}
-}
+
+ public static T CloneByXmlSerialization(this T classToClone) where T : class
+ {
+ var clone = XmlExtensions.SerializeObject(classToClone);
+ return XmlExtensions.DeserializeFromText(clone);
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/CryptExtensions.cs b/UVtools.Core/Extensions/CryptExtensions.cs
index 268d8e1..3962901 100644
--- a/UVtools.Core/Extensions/CryptExtensions.cs
+++ b/UVtools.Core/Extensions/CryptExtensions.cs
@@ -10,115 +10,112 @@ using System;
using System.IO;
using System.Security.Cryptography;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class CryptExtensions
{
- public static class CryptExtensions
+ public static readonly SHA1 SHA1Instance = SHA1.Create();
+ public static string ComputeSHA1Hash(byte[] input)
{
- public static readonly SHA1CryptoServiceProvider SHA1 = new();
- public static string ComputeSHA1Hash(byte[] input)
- {
- return Convert.ToBase64String(SHA1.ComputeHash(input));
- }
-
- public static readonly SHA256 SHA256 = SHA256.Create();
- public static byte[] ComputeSHA256Hash(byte[] input)
- {
- return SHA256.ComputeHash(input);
- }
-
- public static byte[] AesCryptBytes(byte[] data, byte[] key, CipherMode mode, PaddingMode paddingMode, bool encrypt, byte[] iv = null)
- {
- if (data.Length % 16 != 0)
- {
- var temp = new byte[((data.Length / 16) + 1) * 16];
- Array.Copy(data, 0, temp, 0, data.Length);
- data = temp;
- }
-
- var aes = new AesManaged
- {
- KeySize = key.Length * 8,
- Key = key,
- Padding = paddingMode,
- Mode = mode,
- };
-
- if (iv != null)
- {
- aes.IV = iv;
- }
-
- var cryptor = encrypt ? aes.CreateEncryptor() : aes.CreateDecryptor();
-
- using var msDecrypt = new MemoryStream(data);
- using var csDecrypt = new CryptoStream(msDecrypt, cryptor, CryptoStreamMode.Read);
- var outputBuffer = new byte[data.Length];
- csDecrypt.Read(outputBuffer, 0, data.Length);
-
- return outputBuffer;
- }
-
- public static MemoryStream AesCryptMemoryStream(byte[] data, byte[] key, CipherMode mode, PaddingMode paddingMode, bool encrypt, byte[] iv = null)
- => new(AesCryptBytes(data, key, mode, paddingMode, encrypt, iv));
-
- public static string Base64EncodeString(string plainText)
- {
- var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
- return Convert.ToBase64String(plainTextBytes);
- }
-
- public static string Base64DecodeString(string base64EncodedData)
- {
- var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
- return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
- }
-
- public static string XORCipherString(string text, string key)
- {
- var output = new char[text.Length];
-
- for (int i = 0; i < text.Length; i++)
- {
- output[i] = (char)(text[i] ^ key[i % key.Length]);
- }
-
- return new string(output);
- }
-
- public static string XORCipherString(byte[] bytes, string key)
- {
- var output = new char[bytes.Length];
-
- for (int i = 0; i < bytes.Length; i++)
- {
- output[i] = (char)(bytes[i] ^ key[i % key.Length]);
- }
-
- return new string(output);
- }
-
- public static byte[] XORCipher(string text, string key)
- {
- var output = new byte[text.Length];
-
- for (int i = 0; i < text.Length; i++)
- {
- output[i] = (byte)(text[i] ^ key[i % key.Length]);
- }
-
- return output;
- }
-
- public static byte[] XORCipher(byte[] bytes, string key)
- {
- var output = new byte[bytes.Length];
-
- for (int i = 0; i < bytes.Length; i++)
- {
- output[i] = (byte)(bytes[i] ^ key[i % key.Length]);
- }
-
- return output;
- }
+ return Convert.ToBase64String(SHA1Instance.ComputeHash(input));
}
-}
+
+ public static readonly SHA256 SHA256Instance = SHA256.Create();
+ public static byte[] ComputeSHA256Hash(byte[] input)
+ {
+ return SHA256Instance.ComputeHash(input);
+ }
+
+ public static byte[] AesCryptBytes(byte[] data, byte[] key, CipherMode mode, PaddingMode paddingMode, bool encrypt, byte[]? iv = null)
+ {
+ if (data.Length % 16 != 0)
+ {
+ var temp = new byte[((data.Length / 16) + 1) * 16];
+ Array.Copy(data, 0, temp, 0, data.Length);
+ data = temp;
+ }
+
+ var aes = Aes.Create();
+ aes.KeySize = key.Length * 8;
+ aes.Key = key;
+ aes.Padding = paddingMode;
+ aes.Mode = mode;
+
+ if (iv != null)
+ {
+ aes.IV = iv;
+ }
+
+ var cryptor = encrypt ? aes.CreateEncryptor() : aes.CreateDecryptor();
+
+ using var msDecrypt = new MemoryStream(data);
+ using var csDecrypt = new CryptoStream(msDecrypt, cryptor, CryptoStreamMode.Read);
+ var outputBuffer = new byte[data.Length];
+ csDecrypt.Read(outputBuffer, 0, data.Length);
+
+ return outputBuffer;
+ }
+
+ public static MemoryStream AesCryptMemoryStream(byte[] data, byte[] key, CipherMode mode, PaddingMode paddingMode, bool encrypt, byte[]? iv = null)
+ => new(AesCryptBytes(data, key, mode, paddingMode, encrypt, iv));
+
+ public static string Base64EncodeString(string plainText)
+ {
+ var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
+ return Convert.ToBase64String(plainTextBytes);
+ }
+
+ public static string Base64DecodeString(string base64EncodedData)
+ {
+ var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
+ return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
+ }
+
+ public static string XORCipherString(string text, string key)
+ {
+ var output = new char[text.Length];
+
+ for (int i = 0; i < text.Length; i++)
+ {
+ output[i] = (char)(text[i] ^ key[i % key.Length]);
+ }
+
+ return new string(output);
+ }
+
+ public static string XORCipherString(byte[] bytes, string key)
+ {
+ var output = new char[bytes.Length];
+
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ output[i] = (char)(bytes[i] ^ key[i % key.Length]);
+ }
+
+ return new string(output);
+ }
+
+ public static byte[] XORCipher(string text, string key)
+ {
+ var output = new byte[text.Length];
+
+ for (int i = 0; i < text.Length; i++)
+ {
+ output[i] = (byte)(text[i] ^ key[i % key.Length]);
+ }
+
+ return output;
+ }
+
+ public static byte[] XORCipher(byte[] bytes, string key)
+ {
+ var output = new byte[bytes.Length];
+
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ output[i] = (byte)(bytes[i] ^ key[i % key.Length]);
+ }
+
+ return output;
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/DateTimeExtensions.cs b/UVtools.Core/Extensions/DateTimeExtensions.cs
index f2c4b11..ebb132e 100644
--- a/UVtools.Core/Extensions/DateTimeExtensions.cs
+++ b/UVtools.Core/Extensions/DateTimeExtensions.cs
@@ -9,43 +9,42 @@
using System;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class DateTimeExtensions
{
- public static class DateTimeExtensions
+ ///
+ /// Gets the Unix timestamp since Jan 1, 1970 UTC
+ ///
+ public static TimeSpan Timestamp => DateTime.UtcNow.Subtract(DateTime.UnixEpoch);
+
+ ///
+ /// Gets the Unix timestamp in seconds since Jan 1, 1970 UTC
+ ///
+ public static double TimestampSeconds => Timestamp.TotalSeconds;
+
+ ///
+ /// Gets the Unix minutes in seconds since Jan 1, 1970 UTC
+ ///
+ public static double TimestampMinutes => Timestamp.TotalMinutes;
+
+ ///
+ /// Gets the from a unix timestamp in seconds
+ ///
+ ///
+ ///
+ public static DateTime GetDateTimeFromTimestampSeconds(double seconds)
{
- ///
- /// Gets the Unix timestamp since Jan 1, 1970 UTC
- ///
- public static TimeSpan Timestamp => DateTime.UtcNow.Subtract(DateTime.UnixEpoch);
-
- ///
- /// Gets the Unix timestamp in seconds since Jan 1, 1970 UTC
- ///
- public static double TimestampSeconds => Timestamp.TotalSeconds;
-
- ///
- /// Gets the Unix minutes in seconds since Jan 1, 1970 UTC
- ///
- public static double TimestampMinutes => Timestamp.TotalMinutes;
-
- ///
- /// Gets the from a unix timestamp in seconds
- ///
- ///
- ///
- public static DateTime GetDateTimeFromTimestampSeconds(double seconds)
- {
- return DateTime.UnixEpoch.AddSeconds(seconds);
- }
-
- ///
- /// Gets the from a unix timestamp in minutes
- ///
- ///
- ///
- public static DateTime GetDateTimeFromTimestampMinutes(double minutes)
- {
- return DateTime.UnixEpoch.AddMinutes(minutes);
- }
+ return DateTime.UnixEpoch.AddSeconds(seconds);
}
-}
+
+ ///
+ /// Gets the from a unix timestamp in minutes
+ ///
+ ///
+ ///
+ public static DateTime GetDateTimeFromTimestampMinutes(double minutes)
+ {
+ return DateTime.UnixEpoch.AddMinutes(minutes);
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/DrawingExtensions.cs b/UVtools.Core/Extensions/DrawingExtensions.cs
index d8f4385..f0f25bc 100644
--- a/UVtools.Core/Extensions/DrawingExtensions.cs
+++ b/UVtools.Core/Extensions/DrawingExtensions.cs
@@ -1,100 +1,99 @@
using System;
using System.Drawing;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class DrawingExtensions
{
- public static class DrawingExtensions
+ public static Color FactorColor(this Color color, byte pixelColor, byte min = 0, byte max = byte.MaxValue) =>
+ FactorColor(color, pixelColor / 255f, min, max);
+
+ public static Color FactorColor(this Color color, float factor, byte min = 0, byte max = byte.MaxValue)
{
- public static Color FactorColor(this Color color, byte pixelColor, byte min = 0, byte max = byte.MaxValue) =>
- FactorColor(color, pixelColor / 255f, min, max);
+ byte r = (byte)(color.R == 0 ? 0 :
+ Math.Min(Math.Max(min, color.R * factor), max));
- public static Color FactorColor(this Color color, float factor, byte min = 0, byte max = byte.MaxValue)
- {
- byte r = (byte)(color.R == 0 ? 0 :
- Math.Min(Math.Max(min, color.R * factor), max));
-
- byte g = (byte)(color.G == 0 ? 0 :
- Math.Min(Math.Max(min, color.G * factor), max));
+ byte g = (byte)(color.G == 0 ? 0 :
+ Math.Min(Math.Max(min, color.G * factor), max));
- byte b = (byte)(color.B == 0 ? 0 :
- Math.Min(Math.Max(min, color.B * factor), max));
- return Color.FromArgb(r, g, b);
- }
-
- public static double CalculatePolygonSideLengthFromRadius(double radius, int sides)
- {
- return 2 * radius * Math.Sin(Math.PI / sides);
- }
-
- public static double CalculatePolygonVerticalLengthFromRadius(double radius, int sides)
- {
- return radius * Math.Cos(Math.PI / sides);
- }
-
- public static double CalculatePolygonRadiusFromSideLength(double length, int sides)
- {
- var theta = 360.0 / sides;
- return length / (2 * Math.Cos((90 - theta / 2) * Math.PI / 180.0));
- }
-
- public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0, bool flipHorizontally = false, bool flipVertically = false)
- {
- if (sides < 3)
- throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
-
- var vertices = new Point[sides];
-
- double deg = 360.0 / sides;//calculate the rotation angle
- var rad = Math.PI / 180.0;
-
- var x0 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + startingAngle) * rad);
- var y0 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + startingAngle) * rad);
-
- var x1 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + deg + startingAngle) * rad);
- var y1 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + deg + startingAngle) * rad);
-
- vertices[0] = new(
- (int) Math.Round(x0),
- (int) Math.Round(y0)
- );
-
- vertices[1] = new(
- (int) Math.Round(x1),
- (int) Math.Round(y1)
- );
-
- for (int i = 0; i < sides - 2; i++)
- {
- double dsinrot = Math.Sin((deg * (i + 1)) * rad);
- double dcosrot = Math.Cos((deg * (i + 1)) * rad);
-
- vertices[i + 2] = new(
- (int)Math.Round(center.X + dcosrot * (x1 - center.X) - dsinrot * (y1 - center.Y)),
- (int)Math.Round(center.Y + dsinrot * (x1 - center.X) + dcosrot * (y1 - center.Y))
- );
- }
-
- if (flipHorizontally)
- {
- var startX = center.X - radius;
- var endX = center.X + radius;
- for (int i = 0; i < sides; i++)
- {
- vertices[i].X = endX - (vertices[i].X - startX);
- }
- }
-
- if (flipVertically)
- {
- var startY = center.Y - radius;
- var endY = center.Y + radius;
- for (int i = 0; i < sides; i++)
- {
- vertices[i].Y = endY - (vertices[i].Y - startY);
- }
- }
-
- return vertices;
- }
+ byte b = (byte)(color.B == 0 ? 0 :
+ Math.Min(Math.Max(min, color.B * factor), max));
+ return Color.FromArgb(r, g, b);
}
-}
+
+ public static double CalculatePolygonSideLengthFromRadius(double radius, int sides)
+ {
+ return 2 * radius * Math.Sin(Math.PI / sides);
+ }
+
+ public static double CalculatePolygonVerticalLengthFromRadius(double radius, int sides)
+ {
+ return radius * Math.Cos(Math.PI / sides);
+ }
+
+ public static double CalculatePolygonRadiusFromSideLength(double length, int sides)
+ {
+ var theta = 360.0 / sides;
+ return length / (2 * Math.Cos((90 - theta / 2) * Math.PI / 180.0));
+ }
+
+ public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0, bool flipHorizontally = false, bool flipVertically = false)
+ {
+ if (sides < 3)
+ throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
+
+ var vertices = new Point[sides];
+
+ double deg = 360.0 / sides;//calculate the rotation angle
+ var rad = Math.PI / 180.0;
+
+ var x0 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + startingAngle) * rad);
+ var y0 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + startingAngle) * rad);
+
+ var x1 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + deg + startingAngle) * rad);
+ var y1 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + deg + startingAngle) * rad);
+
+ vertices[0] = new(
+ (int) Math.Round(x0),
+ (int) Math.Round(y0)
+ );
+
+ vertices[1] = new(
+ (int) Math.Round(x1),
+ (int) Math.Round(y1)
+ );
+
+ for (int i = 0; i < sides - 2; i++)
+ {
+ double dsinrot = Math.Sin((deg * (i + 1)) * rad);
+ double dcosrot = Math.Cos((deg * (i + 1)) * rad);
+
+ vertices[i + 2] = new(
+ (int)Math.Round(center.X + dcosrot * (x1 - center.X) - dsinrot * (y1 - center.Y)),
+ (int)Math.Round(center.Y + dsinrot * (x1 - center.X) + dcosrot * (y1 - center.Y))
+ );
+ }
+
+ if (flipHorizontally)
+ {
+ var startX = center.X - radius;
+ var endX = center.X + radius;
+ for (int i = 0; i < sides; i++)
+ {
+ vertices[i].X = endX - (vertices[i].X - startX);
+ }
+ }
+
+ if (flipVertically)
+ {
+ var startY = center.Y - radius;
+ var endY = center.Y + radius;
+ for (int i = 0; i < sides; i++)
+ {
+ vertices[i].Y = endY - (vertices[i].Y - startY);
+ }
+ }
+
+ return vertices;
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs
index 730ec12..aae8bd6 100644
--- a/UVtools.Core/Extensions/EmguExtensions.cs
+++ b/UVtools.Core/Extensions/EmguExtensions.cs
@@ -6,1350 +6,1349 @@
* of this license document, but changing it is not allowed.
*/
-using System;
-using System.Drawing;
-using System.Runtime.InteropServices;
using Emgu.CV;
using Emgu.CV.Cuda;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
using UVtools.Core.EmguCV;
using UVtools.Core.Objects;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class EmguExtensions
{
- public static class EmguExtensions
+ #region Constants
+ ///
+ /// White color: 255, 255, 255, 255
+ ///
+ public static readonly MCvScalar WhiteColor = new(255, 255, 255, 255);
+
+ ///
+ /// Black color: 0, 0, 0, 255
+ ///
+ public static readonly MCvScalar BlackColor = new(0, 0, 0, 255);
+ //public static readonly MCvScalar TransparentColor = new();
+
+ public static readonly Mat Kernel3x3Rectangle = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), new Point(-1, -1));
+ #endregion
+
+ #region Initializers methods
+ ///
+ /// Create a byte array of size of this
+ ///
+ ///
+ /// Blank byte array
+ public static byte[] CreateBlankByteArray(this Mat mat)
+ => new byte[mat.GetLength()];
+
+ ///
+ /// Creates a new empty with same size and type of the source
+ ///
+ ///
+ ///
+ public static Mat New(this Mat mat)
+ => new(mat.Size, mat.Depth, mat.NumberOfChannels);
+
+ ///
+ /// Creates a new empty with same size and type of the source
+ ///
+ ///
+ ///
+ ///
+ public static Mat New(this Mat src, MCvScalar color)
+ => InitMat(src.Size, color, src.NumberOfChannels, src.Depth);
+
+ ///
+ /// Creates a new blanked (All zeros) with same size and type of the source
+ ///
+ ///
+ /// Blanked
+ public static Mat NewBlank(this Mat mat)
+ => InitMat(mat.Size, mat.NumberOfChannels, mat.Depth);
+
+ ///
+ /// Creates a new blanked (All zeros) with same size and type of the source
+ ///
+ ///
+ /// Blanked
+ public static UMat NewBlank(this UMat mat)
+ => InitUMat(mat.Size, mat.NumberOfChannels, mat.Depth);
+
+ ///
+ /// Creates a with same size and type of the source and set it to a color
+ ///
+ ///
+ ///
+ ///
+ public static Mat NewSetTo(this Mat mat, MCvScalar color)
+ => InitMat(mat.Size, color, mat.NumberOfChannels, mat.Depth);
+
+
+ ///
+ /// Creates a new and zero it
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Mat InitMat(Size size, int channels = 1, DepthType depthType = DepthType.Cv8U)
+ => size.IsEmpty ? new() : Mat.Zeros(size.Height, size.Width, depthType, channels);
+
+ ///
+ /// Creates a new and zero it
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static UMat InitUMat(Size size, int channels = 1, DepthType depthType = DepthType.Cv8U)
{
- #region Constants
- ///
- /// White color: 255, 255, 255, 255
- ///
- public static readonly MCvScalar WhiteColor = new(255, 255, 255, 255);
+ if (size.IsEmpty) return new();
+ var umat = new UMat(size.Height, size.Width, depthType, channels);
+ umat.SetTo(BlackColor);
+ return umat;
+ }
- ///
- /// Black color: 0, 0, 0, 255
- ///
- public static readonly MCvScalar BlackColor = new(0, 0, 0, 255);
- //public static readonly MCvScalar TransparentColor = new();
+ ///
+ /// Creates a new and set it to a
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Mat InitMat(Size size, MCvScalar color, int channels = 1, DepthType depthType = DepthType.Cv8U)
+ {
+ if (size.IsEmpty) return new();
+ var mat = new Mat(size, depthType, channels);
+ mat.SetTo(color);
+ return mat;
+ }
- public static readonly Mat Kernel3x3Rectangle = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), new Point(-1, -1));
- #endregion
+ ///
+ /// Allocates a new array of mat's
+ ///
+ /// Array size
+ ///
+ public static Mat[] InitMats(uint count) => InitMats(count, Size.Empty);
- #region Initializers methods
- ///
- /// Create a byte array of size of this
- ///
- ///
- /// Blank byte array
- public static byte[] CreateBlankByteArray(this Mat mat)
- => new byte[mat.GetLength()];
-
- ///
- /// Creates a new empty with same size and type of the source
- ///
- ///
- ///
- public static Mat New(this Mat mat)
- => new(mat.Size, mat.Depth, mat.NumberOfChannels);
-
- ///
- /// Creates a new empty with same size and type of the source
- ///
- ///
- ///
- ///
- public static Mat New(this Mat src, MCvScalar color)
- => InitMat(src.Size, color, src.NumberOfChannels, src.Depth);
-
- ///
- /// Creates a new blanked (All zeros) with same size and type of the source
- ///
- ///
- /// Blanked
- public static Mat NewBlank(this Mat mat)
- => InitMat(mat.Size, mat.NumberOfChannels, mat.Depth);
-
- ///
- /// Creates a new blanked (All zeros) with same size and type of the source
- ///
- ///
- /// Blanked
- public static UMat NewBlank(this UMat mat)
- => InitUMat(mat.Size, mat.NumberOfChannels, mat.Depth);
-
- ///
- /// Creates a with same size and type of the source and set it to a color
- ///
- ///
- ///
- ///
- public static Mat NewSetTo(this Mat mat, MCvScalar color)
- => InitMat(mat.Size, color, mat.NumberOfChannels, mat.Depth);
-
-
- ///
- /// Creates a new and zero it
- ///
- ///
- ///
- ///
- ///
- public static Mat InitMat(Size size, int channels = 1, DepthType depthType = DepthType.Cv8U)
- => size.IsEmpty ? new() : Mat.Zeros(size.Height, size.Width, depthType, channels);
-
- ///
- /// Creates a new and zero it
- ///
- ///
- ///
- ///
- ///
- public static UMat InitUMat(Size size, int channels = 1, DepthType depthType = DepthType.Cv8U)
+ ///
+ /// Allocates a new array of mat 's
+ ///
+ /// Array size
+ /// Image size to create, use to create a empty Mat
+ /// New mat array
+ public static Mat[] InitMats(uint count, Size size)
+ {
+ var layers = new Mat[count];
+ for (var i = 0; i < layers.Length; i++)
{
- if (size.IsEmpty) return new();
- var umat = new UMat(size.Height, size.Width, depthType, channels);
- umat.SetTo(BlackColor);
- return umat;
+ layers[i] = InitMat(size);
}
- ///
- /// Creates a new and set it to a
- ///
- ///
- ///
- ///
- ///
- ///
- public static Mat InitMat(Size size, MCvScalar color, int channels = 1, DepthType depthType = DepthType.Cv8U)
+ return layers;
+ }
+
+ ///
+ /// Create a new from
+ ///
+ ///
+ ///
+ public static GpuMat ToGpuMat(this Mat mat)
+ {
+ var gpuMat = new GpuMat(mat.Rows, mat.Cols, mat.Depth, mat.NumberOfChannels);
+ gpuMat.Upload(mat);
+ return gpuMat;
+ }
+ #endregion
+
+ #region Memory accessors
+
+ ///
+ /// Gets the byte pointer of this
+ ///
+ ///
+ ///
+ public static unsafe byte* GetBytePointer(this Mat mat)
+ {
+ return (byte*)mat.DataPointer.ToPointer();
+ }
+
+ ///
+ /// Gets the whole data span to manipulate or read pixels
+ ///
+ ///
+ ///
+ public static unsafe Span GetDataByteSpan(this Mat mat)
+ {
+ return new(mat.DataPointer.ToPointer(), mat.GetLength());
+ }
+
+ public static unsafe Span GetDataByteSpan(this Mat mat, int length, int offset = 0)
+ {
+ return new(IntPtr.Add(mat.DataPointer, offset).ToPointer(), length <= 0 ? mat.GetLength() : length);
+ }
+
+ ///
+ /// Gets the data span to manipulate or read pixels given a length and offset
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static unsafe Span GetDataSpan(this Mat mat, int length = 0, int offset = 0)
+ {
+ return new(IntPtr.Add(mat.DataPointer, offset).ToPointer(), length <= 0 ? mat.GetLength() : length);
+ }
+
+ ///
+ /// Gets a single pixel span to manipulate or read pixels
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Span GetPixelSpan(this Mat mat, int x, int y)
+ {
+ return mat.GetDataSpan(mat.NumberOfChannels, mat.GetPixelPos(x, y));
+ }
+
+ ///
+ /// Gets a single pixel span to manipulate or read pixels
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Span GetPixelSpan(this Mat mat, int pos)
+ {
+ return mat.GetDataSpan(mat.NumberOfChannels, pos);
+ }
+
+ ///
+ /// Gets a row span to manipulate or read pixels
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static unsafe Span GetRowSpan(this Mat mat, int y, int length = 0, int offset = 0)
+ {
+ return new(IntPtr.Add(mat.DataPointer, y * mat.GetRealStep() + offset).ToPointer(), length <= 0 ? mat.GetRealStep() : length);
+ }
+
+ ///
+ /// Gets a col span to manipulate or read pixels
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static unsafe Span GetColSpan(this Mat mat, int x, int length = 0, int offset = 0)
+ {
+ var colMat = mat.Col(x);
+ return new(IntPtr.Add(colMat.DataPointer, offset).ToPointer(), length <= 0 ? mat.Height : length);
+ }
+ #endregion
+
+ #region Memory Fill
+
+ ///
+ /// Fill a mat span with a color
+ ///
+ /// Mat to fill
+ /// Start position, this reference will increment by the
+ /// Length to fill
+ /// Color to fill with
+ /// Ignore and sum to if is less than the threshold value
+ public static void FillSpan(this Mat mat, ref int startPosition, int length, byte color, byte colorFillMinThreshold = 1)
+ {
+ if (length <= 0) return;
+ if (color < colorFillMinThreshold) // Ignore threshold (mostly if blacks), spare cycles
{
- if (size.IsEmpty) return new();
- var mat = new Mat(size, depthType, channels);
- mat.SetTo(color);
- return mat;
- }
-
- ///
- /// Allocates a new array of mat's
- ///
- /// Array size
- ///
- public static Mat[] InitMats(uint count) => InitMats(count, Size.Empty);
-
- ///
- /// Allocates a new array of mat 's
- ///
- /// Array size
- /// Image size to create, use to create a empty Mat
- /// New mat array
- public static Mat[] InitMats(uint count, Size size)
- {
- var layers = new Mat[count];
- for (var i = 0; i < layers.Length; i++)
- {
- layers[i] = InitMat(size);
- }
-
- return layers;
- }
-
- ///
- /// Create a new from
- ///
- ///
- ///
- public static GpuMat ToGpuMat(this Mat mat)
- {
- var gpuMat = new GpuMat(mat.Rows, mat.Cols, mat.Depth, mat.NumberOfChannels);
- gpuMat.Upload(mat);
- return gpuMat;
- }
- #endregion
-
- #region Memory accessors
-
- ///
- /// Gets the byte pointer of this
- ///
- ///
- ///
- public static unsafe byte* GetBytePointer(this Mat mat)
- {
- return (byte*)mat.DataPointer.ToPointer();
- }
-
- ///
- /// Gets the whole data span to manipulate or read pixels
- ///
- ///
- ///
- public static unsafe Span GetDataByteSpan(this Mat mat)
- {
- return new(mat.DataPointer.ToPointer(), mat.GetLength());
- }
-
- public static unsafe Span GetDataByteSpan(this Mat mat, int length, int offset = 0)
- {
- return new(IntPtr.Add(mat.DataPointer, offset).ToPointer(), length <= 0 ? mat.GetLength() : length);
- }
-
- ///
- /// Gets the data span to manipulate or read pixels given a length and offset
- ///
- ///
- ///
- ///
- ///
- ///
- public static unsafe Span GetDataSpan(this Mat mat, int length = 0, int offset = 0)
- {
- return new(IntPtr.Add(mat.DataPointer, offset).ToPointer(), length <= 0 ? mat.GetLength() : length);
- }
-
- ///
- /// Gets a single pixel span to manipulate or read pixels
- ///
- ///
- ///
- ///
- ///
- ///
- public static Span GetPixelSpan(this Mat mat, int x, int y)
- {
- return mat.GetDataSpan(mat.NumberOfChannels, mat.GetPixelPos(x, y));
- }
-
- ///
- /// Gets a single pixel span to manipulate or read pixels
- ///
- ///
- ///
- ///
- ///
- public static Span GetPixelSpan(this Mat mat, int pos)
- {
- return mat.GetDataSpan(mat.NumberOfChannels, pos);
- }
-
- ///
- /// Gets a row span to manipulate or read pixels
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static unsafe Span GetRowSpan(this Mat mat, int y, int length = 0, int offset = 0)
- {
- return new(IntPtr.Add(mat.DataPointer, y * mat.GetRealStep() + offset).ToPointer(), length <= 0 ? mat.GetRealStep() : length);
- }
-
- ///
- /// Gets a col span to manipulate or read pixels
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static unsafe Span GetColSpan(this Mat mat, int x, int length = 0, int offset = 0)
- {
- var colMat = mat.Col(x);
- return new(IntPtr.Add(colMat.DataPointer, offset).ToPointer(), length <= 0 ? mat.Height : length);
- }
- #endregion
-
- #region Memory Fill
-
- ///
- /// Fill a mat span with a color
- ///
- /// Mat to fill
- /// Start position, this reference will increment by the
- /// Length to fill
- /// Color to fill with
- /// Ignore and sum to if is less than the threshold value
- public static void FillSpan(this Mat mat, ref int startPosition, int length, byte color, byte colorFillMinThreshold = 1)
- {
- if (length <= 0) return;
- if (color < colorFillMinThreshold) // Ignore threshold (mostly if blacks), spare cycles
- {
- startPosition += length;
- return;
- }
-
- mat.GetDataByteSpan(length, startPosition).Fill(color);
startPosition += length;
+ return;
}
- ///
- /// Fill a mat span with a color
- ///
- /// Mat to fill
- ///
- ///
- /// Length to fill
- /// Color to fill with
- /// Ignore and sum to if is less than the threshold value
- public static void FillSpan(this Mat mat, int x, int y, int length, byte color, byte colorFillMinThreshold = 1)
+ mat.GetDataByteSpan(length, startPosition).Fill(color);
+ startPosition += length;
+ }
+
+ ///
+ /// Fill a mat span with a color
+ ///
+ /// Mat to fill
+ ///
+ ///
+ /// Length to fill
+ /// Color to fill with
+ /// Ignore and sum to if is less than the threshold value
+ public static void FillSpan(this Mat mat, int x, int y, int length, byte color, byte colorFillMinThreshold = 1)
+ {
+ if (length <= 0 || color < colorFillMinThreshold) return; // Ignore threshold (mostly if blacks), spare cycles
+ mat.GetDataByteSpan(length, mat.GetPixelPos(x, y)).Fill(color);
+ }
+
+ ///
+ /// Fill a mat span with a color
+ ///
+ /// Mat to fill
+ ///
+ /// Length to fill
+ /// Color to fill with
+ /// Ignore and sum to if is less than the threshold value
+ public static void FillSpan(this Mat mat, Point position, int length, byte color, byte colorFillMinThreshold = 1)
+ => mat.FillSpan(position.X, position.Y, length, color, colorFillMinThreshold);
+ #endregion
+
+ #region Get/Set methods
+
+ ///
+ /// .Step return the original Mat step, if ROI step still from original matrix which lead to errors.
+ /// Use this to get the real step size
+ ///
+ ///
+ ///
+ public static int GetRealStep(this Mat mat)
+ {
+ return mat.Width * mat.NumberOfChannels;
+ }
+
+ ///
+ /// Gets the total length of this
+ ///
+ ///
+ /// The total length of this
+ public static int GetLength(this Mat mat)
+ {
+ return mat.Total.ToInt32() * mat.NumberOfChannels;
+ }
+
+ ///
+ /// Gets a pixel index position on a span given X and Y
+ ///
+ ///
+ /// X coordinate
+ /// Y coordinate
+ /// The pixel index position
+ public static int GetPixelPos(this Mat mat, int x, int y)
+ {
+ return y * mat.GetRealStep() + x * mat.NumberOfChannels;
+ }
+
+ ///
+ /// Gets a pixel index position on a span given X and Y
+ ///
+ ///
+ /// X and Y Location
+ /// The pixel index position
+ public static int GetPixelPos(this Mat mat, Point point)
+ {
+ return mat.GetPixelPos(point.X, point.Y);
+ }
+
+ ///
+ /// Gets a byte array copy of this
+ ///
+ ///
+ /// Byte array
+ public static byte[] GetBytes(this Mat mat)
+ {
+ var data = new byte[mat.GetLength()];
+ Marshal.Copy(mat.DataPointer, data, 0, data.Length);
+ return data;
+ }
+
+ ///
+ /// Gets a byte pixel at a position
+ ///
+ ///
+ ///
+ ///
+ public static byte GetByte(this Mat mat, int pos)
+ {
+ //return new Span(IntPtr.Add(mat.DataPointer, pos).ToPointer(), mat.Step)[0];
+ var value = new byte[1];
+ Marshal.Copy(mat.DataPointer + pos, value, 0, value.Length);
+ return value[0];
+ }
+
+ ///
+ /// Gets a byte pixel at a position
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static byte GetByte(this Mat mat, int x, int y) => GetByte(mat, mat.GetPixelPos(x, y));
+
+ ///
+ /// Gets a byte pixel at a position
+ ///
+ ///
+ ///
+ ///
+ public static byte GetByte(this Mat mat, Point pos) => GetByte(mat, mat.GetPixelPos(pos.X, pos.Y));
+
+ ///
+ /// Sets a byte pixel at a position
+ ///
+ ///
+ ///
+ ///
+ public static void SetByte(this Mat mat, int pixel, byte value) => SetByte(mat, pixel, new[] { value });
+
+ ///
+ /// Sets a byte pixel at a position
+ ///
+ ///
+ ///
+ ///
+ public static void SetByte(this Mat mat, int pixel, byte[] value) =>
+ Marshal.Copy(value, 0, mat.DataPointer + pixel, value.Length);
+
+ ///
+ /// Sets a byte pixel at a position
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void SetByte(this Mat mat, int x, int y, byte value) =>
+ SetByte(mat, x, y, new[] { value });
+
+ ///
+ /// Sets a byte pixel at a position
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void SetByte(this Mat mat, int x, int y, byte[] value) =>
+ SetByte(mat, y * mat.GetRealStep() + x * mat.NumberOfChannels, value);
+
+ ///
+ /// Sets bytes
+ ///
+ ///
+ ///
+ public static void SetBytes(this Mat mat, byte[] value) =>
+ Marshal.Copy(value, 0, mat.DataPointer, value.Length);
+
+ ///
+ /// Gets PNG byte array
+ ///
+ ///
+ ///
+ public static byte[] GetPngByes(this IInputArray mat)
+ {
+ return CvInvoke.Imencode(".png", mat);
+ }
+ #endregion
+
+ #region Create methods
+
+ public static Mat CreateMask(this Mat src, VectorOfVectorOfPoint contours)
+ {
+ var mask = src.NewBlank();
+ CvInvoke.DrawContours(mask, contours, -1, WhiteColor, -1);
+ return mask;
+ }
+
+ public static Mat CreateMask(this Mat src, Point[][] contours)
+ {
+ using var vec = new VectorOfVectorOfPoint(contours);
+ return src.CreateMask(vec);
+ }
+
+ #endregion
+
+ #region Copy methods
+ ///
+ /// Copy a region from to center of other
+ ///
+ /// Source to be copied to
+ /// Size of the center offset
+ /// Target to paste the
+ public static void CopyCenterToCenter(this Mat src, Size size, Mat dst)
+ {
+ var srcRoi = src.RoiFromCenter(size);
+ CopyToCenter(srcRoi, dst);
+ }
+
+ ///
+ /// Copy a region from to center of other
+ ///
+ /// Source to be copied to
+ /// Region to copy
+ /// Target to paste the
+ public static void CopyRegionToCenter(this Mat src, Rectangle region, Mat dst)
+ {
+ var srcRoi = src.Roi(region);
+ CopyToCenter(srcRoi, dst);
+ }
+
+ ///
+ /// Copy a to center of other
+ ///
+ /// Source to be copied to
+ /// Target to paste the
+ public static void CopyToCenter(this Mat src, Mat dst)
+ {
+ var srcStep = src.GetRealStep();
+ var dstStep = dst.GetRealStep();
+ var dx = Math.Abs(dstStep - srcStep) / 2;
+ var dy = Math.Abs(dst.Height - src.Height) / 2;
+
+ if (src.Size == dst.Size)
{
- if (length <= 0 || color < colorFillMinThreshold) return; // Ignore threshold (mostly if blacks), spare cycles
- mat.GetDataByteSpan(length, mat.GetPixelPos(x, y)).Fill(color);
+ src.CopyTo(dst);
+ return;
}
- ///
- /// Fill a mat span with a color
- ///
- /// Mat to fill
- ///
- /// Length to fill
- /// Color to fill with
- /// Ignore and sum to if is less than the threshold value
- public static void FillSpan(this Mat mat, Point position, int length, byte color, byte colorFillMinThreshold = 1)
- => mat.FillSpan(position.X, position.Y, length, color, colorFillMinThreshold);
- #endregion
-
- #region Get/Set methods
-
- ///
- /// .Step return the original Mat step, if ROI step still from original matrix which lead to errors.
- /// Use this to get the real step size
- ///
- ///
- ///
- public static int GetRealStep(this Mat mat)
+ if (dstStep > srcStep && dst.Height > src.Height)
{
- return mat.Width * mat.NumberOfChannels;
+ using var dstRoi = dst.Roi(new Rectangle(dx, dy, src.Width, src.Height));
+ src.CopyTo(dstRoi);
+ return;
}
-
- ///
- /// Gets the total length of this
- ///
- ///
- /// The total length of this
- public static int GetLength(this Mat mat)
- {
- return mat.Total.ToInt32() * mat.NumberOfChannels;
- }
-
- ///
- /// Gets a pixel index position on a span given X and Y
- ///
- ///
- /// X coordinate
- /// Y coordinate
- /// The pixel index position
- public static int GetPixelPos(this Mat mat, int x, int y)
- {
- return y * mat.GetRealStep() + x * mat.NumberOfChannels;
- }
-
- ///
- /// Gets a pixel index position on a span given X and Y
- ///
- ///
- /// X and Y Location
- /// The pixel index position
- public static int GetPixelPos(this Mat mat, Point point)
- {
- return mat.GetPixelPos(point.X, point.Y);
- }
-
- ///
- /// Gets a byte array copy of this
- ///
- ///
- /// Byte array
- public static byte[] GetBytes(this Mat mat)
- {
- var data = new byte[mat.GetLength()];
- Marshal.Copy(mat.DataPointer, data, 0, data.Length);
- return data;
- }
-
- ///
- /// Gets a byte pixel at a position
- ///
- ///
- ///
- ///
- public static byte GetByte(this Mat mat, int pos)
- {
- //return new Span(IntPtr.Add(mat.DataPointer, pos).ToPointer(), mat.Step)[0];
- var value = new byte[1];
- Marshal.Copy(mat.DataPointer + pos, value, 0, value.Length);
- return value[0];
- }
-
- ///
- /// Gets a byte pixel at a position
- ///
- ///
- ///
- ///
- ///
- public static byte GetByte(this Mat mat, int x, int y) => GetByte(mat, mat.GetPixelPos(x, y));
-
- ///
- /// Gets a byte pixel at a position
- ///
- ///
- ///
- ///
- public static byte GetByte(this Mat mat, Point pos) => GetByte(mat, mat.GetPixelPos(pos.X, pos.Y));
-
- ///
- /// Sets a byte pixel at a position
- ///
- ///
- ///
- ///
- public static void SetByte(this Mat mat, int pixel, byte value) => SetByte(mat, pixel, new[] { value });
-
- ///
- /// Sets a byte pixel at a position
- ///
- ///
- ///
- ///
- public static void SetByte(this Mat mat, int pixel, byte[] value) =>
- Marshal.Copy(value, 0, mat.DataPointer + pixel, value.Length);
-
- ///
- /// Sets a byte pixel at a position
- ///
- ///
- ///
- ///
- ///
- public static void SetByte(this Mat mat, int x, int y, byte value) =>
- SetByte(mat, x, y, new[] { value });
-
- ///
- /// Sets a byte pixel at a position
- ///
- ///
- ///
- ///
- ///
- public static void SetByte(this Mat mat, int x, int y, byte[] value) =>
- SetByte(mat, y * mat.GetRealStep() + x * mat.NumberOfChannels, value);
-
- ///
- /// Sets bytes
- ///
- ///
- ///
- public static void SetBytes(this Mat mat, byte[] value) =>
- Marshal.Copy(value, 0, mat.DataPointer, value.Length);
-
- ///
- /// Gets PNG byte array
- ///
- ///
- ///
- public static byte[] GetPngByes(this Mat mat)
- {
- return CvInvoke.Imencode(".png", mat);
- }
- #endregion
-
- #region Create methods
-
- public static Mat CreateMask(this Mat src, VectorOfVectorOfPoint contours)
- {
- var mask = src.NewBlank();
- CvInvoke.DrawContours(mask, contours, -1, WhiteColor, -1);
- return mask;
- }
-
- public static Mat CreateMask(this Mat src, Point[][] contours)
- {
- using var vec = new VectorOfVectorOfPoint(contours);
- return src.CreateMask(vec);
- }
-
- #endregion
-
- #region Copy methods
- ///
- /// Copy a region from to center of other
- ///
- /// Source to be copied to
- /// Size of the center offset
- /// Target to paste the
- public static void CopyCenterToCenter(this Mat src, Size size, Mat dst)
- {
- var srcRoi = src.RoiFromCenter(size);
- CopyToCenter(srcRoi, dst);
- }
-
- ///
- /// Copy a region from to center of other
- ///
- /// Source to be copied to
- /// Region to copy
- /// Target to paste the
- public static void CopyRegionToCenter(this Mat src, Rectangle region, Mat dst)
- {
- var srcRoi = src.Roi(region);
- CopyToCenter(srcRoi, dst);
- }
-
- ///
- /// Copy a to center of other
- ///
- /// Source to be copied to
- /// Target to paste the
- public static void CopyToCenter(this Mat src, Mat dst)
- {
- var srcStep = src.GetRealStep();
- var dstStep = dst.GetRealStep();
- var dx = Math.Abs(dstStep - srcStep) / 2;
- var dy = Math.Abs(dst.Height - src.Height) / 2;
-
- if (src.Size == dst.Size)
- {
- src.CopyTo(dst);
- return;
- }
-
- if (dstStep > srcStep && dst.Height > src.Height)
- {
- using var dstRoi = dst.Roi(new Rectangle(dx, dy, src.Width, src.Height));
- src.CopyTo(dstRoi);
- return;
- }
- if (dstStep < srcStep && dst.Height < src.Height)
- {
- using var srcRoi = src.Roi(new Rectangle(dx, dy, dst.Width, dst.Height));
- srcRoi.CopyTo(dst);
- return;
- }
-
- throw new InvalidOperationException("Unable to copy, out of bounds");
+ if (dstStep < srcStep && dst.Height < src.Height)
+ {
+ using var srcRoi = src.Roi(new Rectangle(dx, dy, dst.Width, dst.Height));
+ srcRoi.CopyTo(dst);
+ return;
}
- public static void CopyAreasSmallerThan(this Mat src, double threshold, Mat dst)
+ throw new InvalidOperationException("Unable to copy, out of bounds");
+ }
+
+ public static void CopyAreasSmallerThan(this Mat src, double threshold, Mat dst)
+ {
+ if (threshold <= 1) return;
+ using var contours = src.FindContours(out var hierarchy, RetrType.Tree);
+ var contourGroups = EmguContours.GetContoursInGroups(contours, hierarchy);
+
+ var mask = src.NewBlank();
+ uint drawContours = 0;
+ foreach (var contourGroup in contourGroups)
{
- if (threshold <= 1) return;
- using var contours = src.FindContours(out var hierarchy, RetrType.Tree);
- var contourGroups = EmguContours.GetContoursInGroups(contours, hierarchy);
-
- var mask = src.NewBlank();
- uint drawContours = 0;
- foreach (var contourGroup in contourGroups)
+ using var selectedContours = new VectorOfVectorOfPoint();
+ foreach (var group in contourGroup)
{
- using var selectedContours = new VectorOfVectorOfPoint();
- foreach (var group in contourGroup)
- {
- var area = EmguContours.GetContourArea(group);
- if (area >= threshold) continue;
- drawContours++;
- selectedContours.Push(group);
- }
-
- if (selectedContours.Size == 0) continue;
- CvInvoke.DrawContours(mask, selectedContours, -1, WhiteColor, -1);
-
+ var area = EmguContours.GetContourArea(group);
+ if (area >= threshold) continue;
+ drawContours++;
+ selectedContours.Push(group);
}
- if (drawContours > 0) src.CopyTo(dst, mask);
+
+ if (selectedContours.Size == 0) continue;
+ CvInvoke.DrawContours(mask, selectedContours, -1, WhiteColor, -1);
+
}
+ if (drawContours > 0) src.CopyTo(dst, mask);
+ }
- public static void CopyAreasLargerThan(this Mat src, double threshold, Mat dst)
+ public static void CopyAreasLargerThan(this Mat src, double threshold, Mat dst)
+ {
+ if (threshold <= 0) return;
+ using var contours = src.FindContours(out var hierarchy, RetrType.Tree);
+ var contourGroups = EmguContours.GetContoursInGroups(contours, hierarchy);
+
+ var mask = src.NewBlank();
+ uint drawContours = 0;
+ foreach (var contourGroup in contourGroups)
{
- if (threshold <= 0) return;
- using var contours = src.FindContours(out var hierarchy, RetrType.Tree);
- var contourGroups = EmguContours.GetContoursInGroups(contours, hierarchy);
-
- var mask = src.NewBlank();
- uint drawContours = 0;
- foreach (var contourGroup in contourGroups)
+ using var selectedContours = new VectorOfVectorOfPoint();
+ foreach (var group in contourGroup)
{
- using var selectedContours = new VectorOfVectorOfPoint();
- foreach (var group in contourGroup)
- {
- var area = EmguContours.GetContourArea(group);
- if (area <= threshold) continue;
- drawContours++;
- selectedContours.Push(group);
- }
+ var area = EmguContours.GetContourArea(group);
+ if (area <= threshold) continue;
+ drawContours++;
+ selectedContours.Push(group);
+ }
- if (selectedContours.Size == 0) continue;
- CvInvoke.DrawContours(mask, selectedContours, -1, WhiteColor, -1);
+ if (selectedContours.Size == 0) continue;
+ CvInvoke.DrawContours(mask, selectedContours, -1, WhiteColor, -1);
- }
- if(drawContours > 0) src.CopyTo(dst, mask);
}
- #endregion
+ if(drawContours > 0) src.CopyTo(dst, mask);
+ }
+ #endregion
- #region Roi methods
+ #region Roi methods
- ///
- /// Gets a Roi, but return source when roi is empty or have same size as source
- ///
- ///
- ///
- ///
- public static Mat Roi(this Mat mat, Rectangle roi)
- {
- return roi.IsEmpty || roi.Size == mat.Size ? mat : new Mat(mat, roi);
- }
+ ///
+ /// Gets a Roi, but return source when roi is empty or have same size as source
+ ///
+ ///
+ ///
+ ///
+ public static Mat Roi(this Mat mat, Rectangle roi)
+ {
+ return roi.IsEmpty || roi.Size == mat.Size ? mat : new Mat(mat, roi);
+ }
- ///
- /// Gets a Roi at x=0 and y=0 given a size, but return source when roi is empty or have same size as source
- ///
- ///
- ///
- ///
- public static Mat Roi(this Mat mat, Size size)
- {
- return size.IsEmpty || size == mat.Size ? mat : new Mat(mat, new(Point.Empty, size));
- }
+ ///
+ /// Gets a Roi at x=0 and y=0 given a size, but return source when roi is empty or have same size as source
+ ///
+ ///
+ ///
+ ///
+ public static Mat Roi(this Mat mat, Size size)
+ {
+ return size.IsEmpty || size == mat.Size ? mat : new Mat(mat, new(Point.Empty, size));
+ }
- ///
- /// Gets a Roi from a mat size, but return source when roi is empty or have same size as source
- ///
- ///
- ///
- ///
- public static Mat Roi(this Mat mat, Mat fromMat) => mat.Roi(fromMat.Size);
+ ///
+ /// Gets a Roi from a mat size, but return source when roi is empty or have same size as source
+ ///
+ ///
+ ///
+ ///
+ public static Mat Roi(this Mat mat, Mat fromMat) => mat.Roi(fromMat.Size);
- ///
- /// Gets a Roi from center, but return source when have same size as source
- ///
- ///
- ///
- ///
- public static Mat RoiFromCenter(this Mat mat, Size size)
- {
- if(mat.Size == size) return mat;
+ ///
+ /// Gets a Roi from center, but return source when have same size as source
+ ///
+ ///
+ ///
+ ///
+ public static Mat RoiFromCenter(this Mat mat, Size size)
+ {
+ if(mat.Size == size) return mat;
- var newRoi = mat.Roi(new Rectangle(
- mat.Width / 2 - size.Width / 2,
- mat.Height / 2 - size.Height / 2,
- size.Width,
- size.Height
- ));
+ var newRoi = mat.Roi(new Rectangle(
+ mat.Width / 2 - size.Width / 2,
+ mat.Height / 2 - size.Height / 2,
+ size.Width,
+ size.Height
+ ));
- return newRoi;
- }
+ return newRoi;
+ }
- ///
- /// Gets a new mat obtained from it center at a target size and roi
- ///
- ///
- ///
- ///
- ///
- public static Mat NewMatFromCenterRoi(this Mat mat, Size targetSize, Rectangle roi)
- {
- if (targetSize == mat.Size) return mat.Clone();
- var newMat = InitMat(targetSize);
- var roiMat = mat.Roi(roi);
+ ///
+ /// Gets a new mat obtained from it center at a target size and roi
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Mat NewMatFromCenterRoi(this Mat mat, Size targetSize, Rectangle roi)
+ {
+ if (targetSize == mat.Size) return mat.Clone();
+ var newMat = InitMat(targetSize);
+ var roiMat = mat.Roi(roi);
- //int xStart = mat.Width / 2 - targetSize.Width / 2;
- //int yStart = mat.Height / 2 - targetSize.Height / 2;
- var newMatRoi = newMat.RoiFromCenter(roi.Size);
- /*var newMatRoi = new Mat(newMat, new Rectangle(
- targetSize.Width / 2 - roi.Width / 2,
- targetSize.Height / 2 - roi.Height / 2,
- roi.Width,
- roi.Height
- ));*/
- roiMat.CopyTo(newMatRoi);
- return newMat;
- }
- #endregion
+ //int xStart = mat.Width / 2 - targetSize.Width / 2;
+ //int yStart = mat.Height / 2 - targetSize.Height / 2;
+ var newMatRoi = newMat.RoiFromCenter(roi.Size);
+ /*var newMatRoi = new Mat(newMat, new Rectangle(
+ targetSize.Width / 2 - roi.Width / 2,
+ targetSize.Height / 2 - roi.Height / 2,
+ roi.Width,
+ roi.Height
+ ));*/
+ roiMat.CopyTo(newMatRoi);
+ return newMat;
+ }
+ #endregion
- #region Is methods
+ #region Is methods
- ///
- /// Gets if a is all zeroed by a threshold
- ///
- ///
- /// Pixel brightness threshold
- /// Start pixel position
- /// Pixel span length
- ///
- public static bool IsZeroed(this Mat mat, byte threshold = 0, int startPos = 0, int length = 0)
+ ///
+ /// Gets if a is all zeroed by a threshold
+ ///
+ ///
+ /// Pixel brightness threshold
+ /// Start pixel position
+ /// Pixel span length
+ ///
+ public static bool IsZeroed(this Mat mat, byte threshold = 0, int startPos = 0, int length = 0)
+ {
+ return mat.FindFirstPixelGreaterThan(threshold, startPos, length) == -1;
+ }
+ #endregion
+
+ #region Find methods
+
+ ///
+ /// Finds the first negative (Black) pixel
+ ///
+ ///
+ /// Start pixel position
+ /// Pixel span length
+ /// Pixel position in the span, or -1 if not found
+ public static int FindFirstNegativePixel(this Mat mat, int startPos = 0, int length = 0)
+ {
+ return mat.FindFirstPixelEqualTo(0, startPos, length);
+ }
+
+ ///
+ /// Finds the first positive pixel
+ ///
+ ///
+ /// Start pixel position
+ /// Pixel span length
+ /// Pixel position in the span, or -1 if not found
+ public static int FindFirstPositivePixel(this Mat mat, int startPos = 0, int length = 0)
+ {
+ return mat.FindFirstPixelGreaterThan(0, startPos, length);
+ }
+
+ ///
+ /// Finds the first pixel that is
+ ///
+ ///
+ ///
+ /// Start pixel position
+ /// Pixel span length
+ /// Pixel position in the span, or -1 if not found
+ public static int FindFirstPixelEqualTo(this Mat mat, byte value, int startPos = 0, int length = 0)
+ {
+ var span = mat.GetDataByteSpan();
+ if (length <= 0) length = span.Length;
+ for (var i = startPos; i < length; i++)
{
- return mat.FindFirstPixelGreaterThan(threshold, startPos, length) == -1;
- }
- #endregion
-
- #region Find methods
-
- ///
- /// Finds the first negative (Black) pixel
- ///
- ///
- /// Start pixel position
- /// Pixel span length
- /// Pixel position in the span, or -1 if not found
- public static int FindFirstNegativePixel(this Mat mat, int startPos = 0, int length = 0)
- {
- return mat.FindFirstPixelEqualTo(0, startPos, length);
+ if (span[i] == value) return i;
}
- ///
- /// Finds the first positive pixel
- ///
- ///
- /// Start pixel position
- /// Pixel span length
- /// Pixel position in the span, or -1 if not found
- public static int FindFirstPositivePixel(this Mat mat, int startPos = 0, int length = 0)
+ return -1;
+ }
+
+ ///
+ /// Finds the first pixel that is at less than
+ ///
+ ///
+ ///
+ /// Start pixel position
+ /// Pixel span length
+ /// Pixel position in the span, or -1 if not found
+ public static int FindFirstPixelLessThan(this Mat mat, byte value, int startPos = 0, int length = 0)
+ {
+ var span = mat.GetDataByteSpan();
+ if (length <= 0) length = span.Length;
+ for (var i = startPos; i < length; i++)
{
- return mat.FindFirstPixelGreaterThan(0, startPos, length);
+ if (span[i] < value) return i;
}
- ///
- /// Finds the first pixel that is
- ///
- ///
- ///
- /// Start pixel position
- /// Pixel span length
- /// Pixel position in the span, or -1 if not found
- public static int FindFirstPixelEqualTo(this Mat mat, byte value, int startPos = 0, int length = 0)
- {
- var span = mat.GetDataByteSpan();
- if (length <= 0) length = span.Length;
- for (var i = startPos; i < length; i++)
- {
- if (span[i] == value) return i;
- }
+ return -1;
+ }
- return -1;
+ ///
+ /// Finds the first pixel that is at less or equal than
+ ///
+ ///
+ ///
+ /// Start pixel position
+ /// Pixel span length
+ /// Pixel position in the span, or -1 if not found
+ public static int FindFirstPixelEqualOrLessThan(this Mat mat, byte value, int startPos = 0, int length = 0)
+ {
+ var span = mat.GetDataByteSpan();
+ if (length <= 0) length = span.Length;
+ for (var i = startPos; i < length; i++)
+ {
+ if (span[i] <= value) return i;
}
- ///
- /// Finds the first pixel that is at less than
- ///
- ///
- ///
- /// Start pixel position
- /// Pixel span length
- /// Pixel position in the span, or -1 if not found
- public static int FindFirstPixelLessThan(this Mat mat, byte value, int startPos = 0, int length = 0)
- {
- var span = mat.GetDataByteSpan();
- if (length <= 0) length = span.Length;
- for (var i = startPos; i < length; i++)
- {
- if (span[i] < value) return i;
- }
+ return -1;
+ }
- return -1;
+ ///
+ /// Finds the first pixel that is at greater than
+ ///
+ ///
+ ///
+ /// Start pixel position
+ /// Pixel span length
+ /// Pixel position in the span, or -1 if not found
+ public static int FindFirstPixelGreaterThan(this Mat mat, byte value, int startPos = 0, int length = 0)
+ {
+ var span = mat.GetDataByteSpan();
+ if (length <= 0) length = span.Length;
+ for (var i = startPos; i < length; i++)
+ {
+ if (span[i] > value) return i;
}
- ///
- /// Finds the first pixel that is at less or equal than
- ///
- ///
- ///
- /// Start pixel position
- /// Pixel span length
- /// Pixel position in the span, or -1 if not found
- public static int FindFirstPixelEqualOrLessThan(this Mat mat, byte value, int startPos = 0, int length = 0)
- {
- var span = mat.GetDataByteSpan();
- if (length <= 0) length = span.Length;
- for (var i = startPos; i < length; i++)
- {
- if (span[i] <= value) return i;
- }
+ return -1;
+ }
- return -1;
+ ///
+ /// Finds the first pixel that is at equal or greater than
+ ///
+ ///
+ ///
+ /// Start pixel position
+ /// Pixel span length
+ /// Pixel position in the span, or -1 if not found
+ public static int FindFirstPixelEqualOrGreaterThan(this Mat mat, byte value, int startPos = 0, int length = 0)
+ {
+ var span = mat.GetDataByteSpan();
+ if (length <= 0) length = span.Length;
+ for (var i = startPos; i < length; i++)
+ {
+ if (span[i] >= value) return i;
}
- ///
- /// Finds the first pixel that is at greater than
- ///
- ///
- ///
- /// Start pixel position
- /// Pixel span length
- /// Pixel position in the span, or -1 if not found
- public static int FindFirstPixelGreaterThan(this Mat mat, byte value, int startPos = 0, int length = 0)
- {
- var span = mat.GetDataByteSpan();
- if (length <= 0) length = span.Length;
- for (var i = startPos; i < length; i++)
- {
- if (span[i] > value) return i;
- }
+ return -1;
+ }
+ #endregion
- return -1;
+ #region Transform methods
+ public static void Transform(this Mat src, double xScale, double yScale, double xTrans = 0, double yTrans = 0, Size dstSize = default, Inter interpolation = Inter.Linear)
+ {
+ //var dst = new Mat(src.Size, src.Depth, src.NumberOfChannels);
+ using var translateTransform = new Matrix(2, 3)
+ {
+ [0, 0] = xScale, // xScale
+ [1, 1] = yScale, // yScale
+ [0, 2] = xTrans, //x translation + compensation of x scaling
+ [1, 2] = yTrans // y translation + compensation of y scaling
+ };
+ CvInvoke.WarpAffine(src, src, translateTransform, dstSize.IsEmpty ? src.Size : dstSize, interpolation);
+ }
+
+ ///
+ /// Rotates a Mat by an angle while keeping the image size
+ ///
+ ///
+ /// Angle in degrees to rotate
+ ///
+ ///
+ public static void Rotate(this Mat src, double angle, Size newSize = default, double scale = 1.0) => Rotate(src, src, angle, newSize, scale);
+
+ ///
+ /// Rotates a Mat by an angle while keeping the image size
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void Rotate(this Mat src, Mat dst, double angle, Size newSize = default, double scale = 1.0)
+ {
+ if (angle % 360 == 0 && scale == 1.0) return;
+ if (newSize.IsEmpty)
+ {
+ newSize = src.Size;
}
-
- ///
- /// Finds the first pixel that is at equal or greater than
- ///
- ///
- ///
- /// Start pixel position
- /// Pixel span length
- /// Pixel position in the span, or -1 if not found
- public static int FindFirstPixelEqualOrGreaterThan(this Mat mat, byte value, int startPos = 0, int length = 0)
- {
- var span = mat.GetDataByteSpan();
- if (length <= 0) length = span.Length;
- for (var i = startPos; i < length; i++)
- {
- if (span[i] >= value) return i;
- }
-
- return -1;
- }
- #endregion
-
- #region Transform methods
- public static void Transform(this Mat src, double xScale, double yScale, double xTrans = 0, double yTrans = 0, Size dstSize = default, Inter interpolation = Inter.Linear)
- {
- //var dst = new Mat(src.Size, src.Depth, src.NumberOfChannels);
- using var translateTransform = new Matrix(2, 3)
- {
- [0, 0] = xScale, // xScale
- [1, 1] = yScale, // yScale
- [0, 2] = xTrans, //x translation + compensation of x scaling
- [1, 2] = yTrans // y translation + compensation of y scaling
- };
- CvInvoke.WarpAffine(src, src, translateTransform, dstSize.IsEmpty ? src.Size : dstSize, interpolation);
- }
-
- ///
- /// Rotates a Mat by an angle while keeping the image size
- ///
- ///
- /// Angle in degrees to rotate
- ///
- ///
- public static void Rotate(this Mat src, double angle, Size newSize = default, double scale = 1.0) => Rotate(src, src, angle, newSize, scale);
-
- ///
- /// Rotates a Mat by an angle while keeping the image size
- ///
- ///
- ///
- ///
- ///
- public static void Rotate(this Mat src, Mat dst, double angle, Size newSize = default, double scale = 1.0)
- {
- if (angle % 360 == 0 && scale == 1.0) return;
- if (newSize.IsEmpty)
- {
- newSize = src.Size;
- }
- var halfWidth = src.Width / 2.0f;
- var halfHeight = src.Height / 2.0f;
- using var translateTransform = new Matrix(2, 3);
- CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), -angle, scale, translateTransform);
+ var halfWidth = src.Width / 2.0f;
+ var halfHeight = src.Height / 2.0f;
+ using var translateTransform = new Matrix(2, 3);
+ CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), -angle, scale, translateTransform);
- if (src.Size != newSize)
- {
- // adjust the rotation matrix to take into account translation
- translateTransform[0, 2] += newSize.Width / 2.0 - halfWidth;
- translateTransform[1, 2] += newSize.Height / 2.0 - halfHeight;
- }
-
- CvInvoke.WarpAffine(src, dst, translateTransform, newSize);
- }
-
- ///
- /// Rotates a Mat by an angle while adjusting bounds to fit the rotated content
- ///
- ///
- ///
- ///
- public static void RotateAdjustBounds(this Mat src, double angle, double scale = 1.0) => RotateAdjustBounds(src, src, angle, scale);
-
- ///
- /// Rotates a Mat by an angle while adjusting bounds to fit the rotated content
- ///
- ///
- ///
- ///
- ///
- public static void RotateAdjustBounds(this Mat src, Mat dst, double angle, double scale = 1.0)
+ if (src.Size != newSize)
{
- if (angle % 360 == 0 && scale == 1.0) return;
- var halfWidth = src.Width / 2.0f;
- var halfHeight = src.Height / 2.0f;
- using var translateTransform = new Matrix(2, 3);
- CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), -angle, scale, translateTransform);
- var cos = Math.Abs(translateTransform[0, 0]);
- var sin = Math.Abs(translateTransform[0, 1]);
-
- // compute the new bounding dimensions of the image
- int newWidth = (int) (src.Height * sin + src.Width * cos);
- int newHeight = (int) (src.Height * cos + src.Width * sin);
-
// adjust the rotation matrix to take into account translation
- translateTransform[0, 2] += newWidth / 2.0 - halfWidth;
- translateTransform[1, 2] += newHeight / 2.0 - halfHeight;
-
-
- CvInvoke.WarpAffine(src, dst, translateTransform, new Size(newWidth, newHeight));
+ translateTransform[0, 2] += newSize.Width / 2.0 - halfWidth;
+ translateTransform[1, 2] += newSize.Height / 2.0 - halfHeight;
}
- ///
- /// Scale image from it center, preserving src bounds
- /// https://stackoverflow.com/a/62543674/933976
- ///
- /// to transform
- /// X scale factor
- /// Y scale factor
- /// X translation
- /// Y translation
- /// Destination size
- /// Interpolation mode
- public static void TransformFromCenter(this Mat src, double xScale, double yScale, double xTrans = 0, double yTrans = 0, Size dstSize = default, Inter interpolation = Inter.Linear)
+ CvInvoke.WarpAffine(src, dst, translateTransform, newSize);
+ }
+
+ ///
+ /// Rotates a Mat by an angle while adjusting bounds to fit the rotated content
+ ///
+ ///
+ ///
+ ///
+ public static void RotateAdjustBounds(this Mat src, double angle, double scale = 1.0) => RotateAdjustBounds(src, src, angle, scale);
+
+ ///
+ /// Rotates a Mat by an angle while adjusting bounds to fit the rotated content
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void RotateAdjustBounds(this Mat src, Mat dst, double angle, double scale = 1.0)
+ {
+ if (angle % 360 == 0 && scale == 1.0) return;
+ var halfWidth = src.Width / 2.0f;
+ var halfHeight = src.Height / 2.0f;
+ using var translateTransform = new Matrix(2, 3);
+ CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), -angle, scale, translateTransform);
+ var cos = Math.Abs(translateTransform[0, 0]);
+ var sin = Math.Abs(translateTransform[0, 1]);
+
+ // compute the new bounding dimensions of the image
+ int newWidth = (int) (src.Height * sin + src.Width * cos);
+ int newHeight = (int) (src.Height * cos + src.Width * sin);
+
+ // adjust the rotation matrix to take into account translation
+ translateTransform[0, 2] += newWidth / 2.0 - halfWidth;
+ translateTransform[1, 2] += newHeight / 2.0 - halfHeight;
+
+
+ CvInvoke.WarpAffine(src, dst, translateTransform, new Size(newWidth, newHeight));
+ }
+
+ ///
+ /// Scale image from it center, preserving src bounds
+ /// https://stackoverflow.com/a/62543674/933976
+ ///
+ /// to transform
+ /// X scale factor
+ /// Y scale factor
+ /// X translation
+ /// Y translation
+ /// Destination size
+ /// Interpolation mode
+ public static void TransformFromCenter(this Mat src, double xScale, double yScale, double xTrans = 0, double yTrans = 0, Size dstSize = default, Inter interpolation = Inter.Linear)
+ {
+ src.Transform(xScale, yScale,
+ xTrans + (src.Width - src.Width * xScale) / 2.0,
+ yTrans + (src.Height - src.Height * yScale) / 2.0, dstSize, interpolation);
+ }
+ #endregion
+
+ #region Draw Methods
+
+ ///
+ /// Draw a rotated square around a center point
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void DrawRotatedSquare(this Mat src, int size, Point center, MCvScalar color, int angle = 0, int thickness = -1, LineType lineType = LineType.EightConnected)
+ => src.DrawRotatedRectangle(new(size, size), center, color, angle, thickness, lineType);
+
+ ///
+ /// Draw a rotated rectangle around a center point
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void DrawRotatedRectangle(this Mat src, Size size, Point center, MCvScalar color, int angle = 0, int thickness = -1, LineType lineType = LineType.EightConnected)
+ {
+ var rect = new RotatedRect(center, size, angle);
+ var vertices = rect.GetVertices();
+ var points = new Point[vertices.Length];
+
+ for (int i = 0; i < vertices.Length; i++)
{
- src.Transform(xScale, yScale,
- xTrans + (src.Width - src.Width * xScale) / 2.0,
- yTrans + (src.Height - src.Height * yScale) / 2.0, dstSize, interpolation);
- }
- #endregion
-
- #region Draw Methods
-
- ///
- /// Draw a rotated square around a center point
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static void DrawRotatedSquare(this Mat src, int size, Point center, MCvScalar color, int angle = 0, int thickness = -1, LineType lineType = LineType.EightConnected)
- => src.DrawRotatedRectangle(new(size, size), center, color, angle, thickness, lineType);
-
- ///
- /// Draw a rotated rectangle around a center point
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static void DrawRotatedRectangle(this Mat src, Size size, Point center, MCvScalar color, int angle = 0, int thickness = -1, LineType lineType = LineType.EightConnected)
- {
- var rect = new RotatedRect(center, size, angle);
- var vertices = rect.GetVertices();
- var points = new Point[vertices.Length];
-
- for (int i = 0; i < vertices.Length; i++)
- {
- points[i] = new(
- (int)Math.Round(vertices[i].X),
- (int)Math.Round(vertices[i].Y)
- );
- }
-
- if (thickness <= 0)
- {
- using var vec = new VectorOfPoint(points);
- CvInvoke.FillConvexPoly(src, vec, color, lineType);
- }
- else
- {
- CvInvoke.Polylines(src, points, true, color, thickness, lineType);
- }
+ points[i] = new(
+ (int)Math.Round(vertices[i].X),
+ (int)Math.Round(vertices[i].Y)
+ );
}
- ///
- /// Draw a polygon given number of sides and length
- ///
- ///
- /// Number of polygon sides, Special: use 1 to draw a line and >= 100 to draw a native OpenCV circle
- /// Radius
- /// Center position
- ///
- ///
- ///
- ///
- ///
- public static void DrawPolygon(this Mat src, int sides, int radius, Point center, MCvScalar color, double startingAngle = 0, int thickness = -1, LineType lineType = LineType.EightConnected, FlipType? flip = null)
+ if (thickness <= 0)
{
- if (sides == 1)
+ using var vec = new VectorOfPoint(points);
+ CvInvoke.FillConvexPoly(src, vec, color, lineType);
+ }
+ else
+ {
+ CvInvoke.Polylines(src, points, true, color, thickness, lineType);
+ }
+ }
+
+ ///
+ /// Draw a polygon given number of sides and length
+ ///
+ ///
+ /// Number of polygon sides, Special: use 1 to draw a line and >= 100 to draw a native OpenCV circle
+ /// Radius
+ /// Center position
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void DrawPolygon(this Mat src, int sides, int radius, Point center, MCvScalar color, double startingAngle = 0, int thickness = -1, LineType lineType = LineType.EightConnected, FlipType? flip = null)
+ {
+ if (sides == 1)
+ {
+ var point1 = new Point(center.X - radius, center.Y);
+ var point2 = new Point(center.X + radius, center.Y);
+ point1 = point1.Rotate(startingAngle, center);
+ point2 = point2.Rotate(startingAngle, center);
+
+ if (flip is FlipType.Horizontal or FlipType.Both)
{
- var point1 = new Point(center.X - radius, center.Y);
- var point2 = new Point(center.X + radius, center.Y);
- point1 = point1.Rotate(startingAngle, center);
- point2 = point2.Rotate(startingAngle, center);
-
- if (flip is FlipType.Horizontal or FlipType.Both)
- {
- var newPoint1 = new Point(point2.X, point1.Y);
- var newPoint2 = new Point(point1.X, point2.Y);
- point1 = newPoint1;
- point2 = newPoint2;
- }
-
- if (flip is FlipType.Vertical or FlipType.Both)
- {
- var newPoint1 = new Point(point1.X, point2.Y);
- var newPoint2 = new Point(point2.X, point1.Y);
- point1 = newPoint1;
- point2 = newPoint2;
- }
-
- CvInvoke.Line(src, point1, point2, color, thickness < 1 ? 1 : thickness, lineType);
- return;
- }
- if (sides >= 100)
- {
- CvInvoke.Circle(src, center, radius, color, thickness, lineType);
- return;
+ var newPoint1 = new Point(point2.X, point1.Y);
+ var newPoint2 = new Point(point1.X, point2.Y);
+ point1 = newPoint1;
+ point2 = newPoint2;
}
- var points = DrawingExtensions.GetPolygonVertices(sides, radius, center, startingAngle,
- flip is FlipType.Horizontal or FlipType.Both,
- flip is FlipType.Vertical or FlipType.Both);
+ if (flip is FlipType.Vertical or FlipType.Both)
+ {
+ var newPoint1 = new Point(point1.X, point2.Y);
+ var newPoint2 = new Point(point2.X, point1.Y);
+ point1 = newPoint1;
+ point2 = newPoint2;
+ }
+
+ CvInvoke.Line(src, point1, point2, color, thickness < 1 ? 1 : thickness, lineType);
+ return;
+ }
+ if (sides >= 100)
+ {
+ CvInvoke.Circle(src, center, radius, color, thickness, lineType);
+ return;
+ }
+
+ var points = DrawingExtensions.GetPolygonVertices(sides, radius, center, startingAngle,
+ flip is FlipType.Horizontal or FlipType.Both,
+ flip is FlipType.Vertical or FlipType.Both);
- if (thickness <= 0)
- {
- using var vec = new VectorOfPoint(points);
- CvInvoke.FillConvexPoly(src, vec, color, lineType);
- }
- else
- {
- CvInvoke.Polylines(src, points, true, color, thickness, lineType);
- }
+ if (thickness <= 0)
+ {
+ using var vec = new VectorOfPoint(points);
+ CvInvoke.FillConvexPoly(src, vec, color, lineType);
+ }
+ else
+ {
+ CvInvoke.Polylines(src, points, true, color, thickness, lineType);
+ }
- }
- #endregion
+ }
+ #endregion
- #region Text methods
- public enum PutTextLineAlignment : byte
+ #region Text methods
+ public enum PutTextLineAlignment : byte
+ {
+ ///
+ /// Left aligned without trimming, openCV default call
+ ///
+ None,
+
+ ///
+ /// Left aligned and trimmed
+ ///
+ Left,
+
+ ///
+ /// Center aligned and trimmed
+ ///
+ Center,
+
+ ///
+ /// Right aligned and trimmed
+ ///
+ Right
+ }
+
+ public static string PutTextLineAlignmentTrim(string line, PutTextLineAlignment lineAlignment)
+ {
+ switch (lineAlignment)
{
- ///
- /// Left aligned without trimming, openCV default call
- ///
- None,
+ case PutTextLineAlignment.None:
+ return line.TrimEnd();
+ case PutTextLineAlignment.Left:
+ case PutTextLineAlignment.Center:
+ case PutTextLineAlignment.Right:
+ return line.Trim();
+ default:
+ throw new ArgumentOutOfRangeException(nameof(lineAlignment), lineAlignment, null);
+ }
+ }
- ///
- /// Left aligned and trimmed
- ///
- Left,
+ public static Size GetTextSizeExtended(string text, FontFace fontFace, double fontScale, int thickness, ref int baseLine, PutTextLineAlignment lineAlignment = default)
+ => GetTextSizeExtended(text, fontFace, fontScale, thickness, 0, ref baseLine, lineAlignment);
+ public static Size GetTextSizeExtended(string text, FontFace fontFace, double fontScale, int thickness, int lineGapOffset, ref int baseLine, PutTextLineAlignment lineAlignment = default)
+ {
+ text = text.TrimEnd('\n', '\r', ' ');
+ var lines = text.Split(StaticObjects.LineBreakCharacters, StringSplitOptions.None);
+ var textSize = CvInvoke.GetTextSize(text, fontFace, fontScale, thickness, ref baseLine);
- ///
- /// Center aligned and trimmed
- ///
- Center,
+ if (lines.Length is 0 or 1) return textSize;
- ///
- /// Right aligned and trimmed
- ///
- Right
+ var lineGap = textSize.Height / 3 + lineGapOffset;
+ var width = 0;
+ var height = lines.Length * (lineGap + textSize.Height) - lineGap;
+
+ for (var i = 0; i < lines.Length; i++)
+ {
+ lines[i] = PutTextLineAlignmentTrim(lines[i], lineAlignment);
+
+ if (string.IsNullOrWhiteSpace(lines[i])) continue;
+ int baseLineRef = 0;
+ var lineSize = CvInvoke.GetTextSize(lines[i], fontFace, fontScale, thickness, ref baseLineRef);
+ width = Math.Max(width, lineSize.Width);
}
- public static string PutTextLineAlignmentTrim(string line, PutTextLineAlignment lineAlignment)
+
+ return new(width, height);
+ }
+
+ public static void PutTextExtended(this IInputOutputArray src, string text, Point org, FontFace fontFace, double fontScale,
+ MCvScalar color, int thickness = 1, LineType lineType = LineType.EightConnected,
+ bool bottomLeftOrigin = false, PutTextLineAlignment lineAlignment = default)
+ => src.PutTextExtended(text, org, fontFace, fontScale, color, thickness, 0, lineType, bottomLeftOrigin, lineAlignment);
+
+ ///
+ /// Extended OpenCV PutText to accepting line breaks and line alignment
+ ///
+ public static void PutTextExtended(this IInputOutputArray src, string text, Point org, FontFace fontFace, double fontScale,
+ MCvScalar color, int thickness = 1, int lineGapOffset = 0, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false, PutTextLineAlignment lineAlignment = default)
+ {
+ text = text.TrimEnd('\n', '\r', ' ');
+ var lines = text.Split(StaticObjects.LineBreakCharacters, StringSplitOptions.None);
+
+ switch (lines.Length)
{
- switch (lineAlignment)
- {
- case PutTextLineAlignment.None:
- return line.TrimEnd();
- case PutTextLineAlignment.Left:
- case PutTextLineAlignment.Center:
- case PutTextLineAlignment.Right:
- return line.Trim();
- default:
- throw new ArgumentOutOfRangeException(nameof(lineAlignment), lineAlignment, null);
- }
+ case 0:
+ return;
+ case 1:
+ CvInvoke.PutText(src, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin);
+ return;
}
- public static Size GetTextSizeExtended(string text, FontFace fontFace, double fontScale, int thickness, ref int baseLine, PutTextLineAlignment lineAlignment = default)
- => GetTextSizeExtended(text, fontFace, fontScale, thickness, 0, ref baseLine, lineAlignment);
- public static Size GetTextSizeExtended(string text, FontFace fontFace, double fontScale, int thickness, int lineGapOffset, ref int baseLine, PutTextLineAlignment lineAlignment = default)
+ // Get height of text lines in pixels (height of all lines is the same)
+ int baseLine = 0;
+ var textSize = CvInvoke.GetTextSize(text, fontFace, fontScale, thickness, ref baseLine);
+ var lineGap = textSize.Height / 3 + lineGapOffset;
+ var linesSize = new Size[lines.Length];
+ int width = 0;
+
+ // Sanitize lines
+ for (var i = 0; i < lines.Length; i++)
{
- text = text.TrimEnd('\n', '\r', ' ');
- var lines = text.Split(StaticObjects.LineBreakCharacters, StringSplitOptions.None);
- var textSize = CvInvoke.GetTextSize(text, fontFace, fontScale, thickness, ref baseLine);
-
- if (lines.Length is 0 or 1) return textSize;
-
- var lineGap = textSize.Height / 3 + lineGapOffset;
- var width = 0;
- var height = lines.Length * (lineGap + textSize.Height) - lineGap;
+ lines[i] = PutTextLineAlignmentTrim(lines[i], lineAlignment);
+ }
+ // If line needs alignment, calculate the size for each line
+ if (lineAlignment is not PutTextLineAlignment.Left and not PutTextLineAlignment.None)
+ {
for (var i = 0; i < lines.Length; i++)
{
- lines[i] = PutTextLineAlignmentTrim(lines[i], lineAlignment);
-
if (string.IsNullOrWhiteSpace(lines[i])) continue;
int baseLineRef = 0;
- var lineSize = CvInvoke.GetTextSize(lines[i], fontFace, fontScale, thickness, ref baseLineRef);
- width = Math.Max(width, lineSize.Width);
+ linesSize[i] = CvInvoke.GetTextSize(lines[i], fontFace, fontScale, thickness, ref baseLineRef);
+ width = Math.Max(width, linesSize[i].Width);
}
-
-
- return new(width, height);
}
- public static void PutTextExtended(this Mat src, string text, Point org, FontFace fontFace, double fontScale,
- MCvScalar color, int thickness = 1, LineType lineType = LineType.EightConnected,
- bool bottomLeftOrigin = false, PutTextLineAlignment lineAlignment = default)
- => src.PutTextExtended(text, org, fontFace, fontScale, color, thickness, 0, lineType, bottomLeftOrigin, lineAlignment);
-
- ///
- /// Extended OpenCV PutText to accepting line breaks and line alignment
- ///
- public static void PutTextExtended(this Mat src, string text, Point org, FontFace fontFace, double fontScale,
- MCvScalar color, int thickness = 1, int lineGapOffset = 0, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false, PutTextLineAlignment lineAlignment = default)
+ for (var i = 0; i < lines.Length; i++)
{
- text = text.TrimEnd('\n', '\r', ' ');
- var lines = text.Split(StaticObjects.LineBreakCharacters, StringSplitOptions.None);
-
- switch (lines.Length)
- {
- case 0:
- return;
- case 1:
- CvInvoke.PutText(src, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin);
- return;
- }
-
- // Get height of text lines in pixels (height of all lines is the same)
- int baseLine = 0;
- var textSize = CvInvoke.GetTextSize(text, fontFace, fontScale, thickness, ref baseLine);
- var lineGap = textSize.Height / 3 + lineGapOffset;
- var linesSize = new Size[lines.Length];
- int width = 0;
-
- // Sanitize lines
- for (var i = 0; i < lines.Length; i++)
- {
- lines[i] = PutTextLineAlignmentTrim(lines[i], lineAlignment);
- }
-
- // If line needs alignment, calculate the size for each line
- if (lineAlignment is not PutTextLineAlignment.Left and not PutTextLineAlignment.None)
- {
- for (var i = 0; i < lines.Length; i++)
- {
- if (string.IsNullOrWhiteSpace(lines[i])) continue;
- int baseLineRef = 0;
- linesSize[i] = CvInvoke.GetTextSize(lines[i], fontFace, fontScale, thickness, ref baseLineRef);
- width = Math.Max(width, linesSize[i].Width);
- }
- }
-
- for (var i = 0; i < lines.Length; i++)
- {
- if(string.IsNullOrWhiteSpace(lines[i])) continue;
+ if(string.IsNullOrWhiteSpace(lines[i])) continue;
- int x = lineAlignment switch
- {
- PutTextLineAlignment.None or PutTextLineAlignment.Left => org.X,
- PutTextLineAlignment.Center => org.X + (width - linesSize[i].Width) / 2,
- PutTextLineAlignment.Right => org.X + width - linesSize[i].Width,
- _ => throw new ArgumentOutOfRangeException(nameof(lineAlignment), lineAlignment, null)
- };
-
- // Find total size of text block before this line
- var lineYAdjustment = i * (lineGap + textSize.Height);
- // Move text down from original line based on line number
- int lineY = !bottomLeftOrigin ? org.Y + lineYAdjustment : org.Y - lineYAdjustment;
- CvInvoke.PutText(src, lines[i], new Point(x, lineY), fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin);
- }
- }
-
- ///
- /// Extended OpenCV PutText to accepting line breaks, line alignment and rotation
- ///
- public static void PutTextRotated(this Mat src, string text, Point org, FontFace fontFace, double fontScale,
- MCvScalar color,
- int thickness = 1, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false,
- PutTextLineAlignment lineAlignment = default, double angle = 0)
- => src.PutTextRotated(text, org, fontFace, fontScale, color, thickness, 0, lineType, bottomLeftOrigin,
- lineAlignment, angle);
-
- ///
- /// Extended OpenCV PutText to accepting line breaks, line alignment and rotation
- ///
- public static void PutTextRotated(this Mat src, string text, Point org, FontFace fontFace, double fontScale, MCvScalar color,
- int thickness = 1, int lineGapOffset = 0, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false, PutTextLineAlignment lineAlignment = default, double angle = 0)
- {
- if (angle % 360 == 0) // No rotation needed, cheaper cycle
+ int x = lineAlignment switch
{
- src.PutTextExtended(text, org, fontFace, fontScale, color, thickness, lineGapOffset, lineType, bottomLeftOrigin, lineAlignment);
- return;
- }
+ PutTextLineAlignment.None or PutTextLineAlignment.Left => org.X,
+ PutTextLineAlignment.Center => org.X + (width - linesSize[i].Width) / 2,
+ PutTextLineAlignment.Right => org.X + width - linesSize[i].Width,
+ _ => throw new ArgumentOutOfRangeException(nameof(lineAlignment), lineAlignment, null)
+ };
- using var rotatedSrc = src.Clone();
- rotatedSrc.RotateAdjustBounds(-angle);
- var sizeDifference = (rotatedSrc.Size - src.Size).Half();
- org.Offset(sizeDifference.ToPoint());
- org = org.Rotate(-angle, new Point(rotatedSrc.Size.Width / 2, rotatedSrc.Size.Height / 2));
- rotatedSrc.PutTextExtended(text, org, fontFace, fontScale, color, thickness, lineGapOffset, lineType, bottomLeftOrigin, lineAlignment);
-
- using var mask = rotatedSrc.NewBlank();
- mask.PutTextExtended(text, org, fontFace, fontScale, WhiteColor, thickness, lineGapOffset, lineType, bottomLeftOrigin, lineAlignment);
-
- rotatedSrc.Rotate(angle, src.Size);
- mask.Rotate(angle, src.Size);
-
- rotatedSrc.CopyTo(src, mask);
+ // Find total size of text block before this line
+ var lineYAdjustment = i * (lineGap + textSize.Height);
+ // Move text down from original line based on line number
+ int lineY = !bottomLeftOrigin ? org.Y + lineYAdjustment : org.Y - lineYAdjustment;
+ CvInvoke.PutText(src, lines[i], new Point(x, lineY), fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin);
}
- #endregion
-
- #region Utilities methods
-
- ///
- /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,].
- /// The function modifies the source image content
- ///
- /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content
- /// Retrieval mode
- /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation).
- /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context
- /// The contour hierarchy
- public static VectorOfVectorOfPoint FindContours(this Mat mat, RetrType mode = RetrType.List, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default)
- {
- using var hierarchy = new Mat();
- var contours = new VectorOfVectorOfPoint();
- CvInvoke.FindContours(mat, contours, hierarchy, mode, method, offset);
- return contours;
- }
-
- /*
- ///
- /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,].
- /// The function modifies the source image content
- ///
- /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content
- /// Detected contours. Each contour is stored as a vector of points.
- /// Retrieval mode
- /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation).
- /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context
- /// The contour hierarchy
- public static int[,] FindContours(this Mat mat, IOutputArray contours, RetrType mode, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default)
- {
- using var hierarchy = new Mat();
- CvInvoke.FindContours(mat, contours, hierarchy, mode, method, offset);
- var numArray = new int[hierarchy.Cols, 4];
- var gcHandle = GCHandle.Alloc(numArray, GCHandleType.Pinned);
- using (var mat2 = new Mat(hierarchy.Rows, hierarchy.Cols, hierarchy.Depth, 4, gcHandle.AddrOfPinnedObject(), hierarchy.Step))
- hierarchy.CopyTo(mat2);
- gcHandle.Free();
- return numArray;
- }*/
-
- ///
- /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,].
- /// The function modifies the source image content
- ///
- /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content
- /// The contour hierarchy
- /// Retrieval mode
- /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation).
- /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context
- /// Detected contours. Each contour is stored as a vector of points.
- public static VectorOfVectorOfPoint FindContours(this Mat mat, out int[,] hierarchy, RetrType mode, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default)
- {
- var contours = new VectorOfVectorOfPoint();
- using var hierarchyMat = new Mat();
- CvInvoke.FindContours(mat, contours, hierarchyMat, mode, method, offset);
- hierarchy = new int[hierarchyMat.Cols, 4];
- var gcHandle = GCHandle.Alloc(hierarchy, GCHandleType.Pinned);
- using (var mat2 = new Mat(hierarchyMat.Rows, hierarchyMat.Cols, hierarchyMat.Depth, 4, gcHandle.AddrOfPinnedObject(), hierarchyMat.Step))
- hierarchyMat.CopyTo(mat2);
- gcHandle.Free();
- return contours;
- }
-
-
- ///
- /// Determine the area (i.e. total number of pixels in the image),
- /// initialize the output skeletonized image, and construct the
- /// morphological structuring element
- ///
- ///
- /// Number of iterations required to perform the skeletoize
- ///
- ///
- public static Mat Skeletonize(this Mat src, out int iterations, Size ksize = default, ElementShape elementShape = ElementShape.Rectangle)
- {
- if (ksize.IsEmpty) ksize = new Size(3, 3);
- Point anchor = new(-1, -1);
- var skeleton = src.NewBlank();
- var kernel = Kernel3x3Rectangle;
-
- var image = src;
- using var temp = new Mat();
- iterations = 0;
- while (true)
- {
- iterations++;
-
- // erode and dilate the image using the structuring element
- using var eroded = new Mat();
- CvInvoke.Erode(image, eroded, kernel, anchor, 1, BorderType.Reflect101, default);
- CvInvoke.Dilate(eroded, temp, kernel, anchor, 1, BorderType.Reflect101, default);
-
- // subtract the temporary image from the original, eroded
- // image, then take the bitwise 'or' between the skeleton
- // and the temporary image
- CvInvoke.Subtract(image, temp, temp);
- CvInvoke.BitwiseOr(skeleton, temp, skeleton);
- image = eroded.Clone();
-
- // if there are no more 'white' pixels in the image, then
- // break from the loop
- if (CvInvoke.CountNonZero(image) == 0) break;
- }
-
- return skeleton;
- }
-
- ///
- /// Determine the area (i.e. total number of pixels in the image),
- /// initialize the output skeletonized image, and construct the
- /// morphological structuring element
- ///
- ///
- ///
- ///
- public static Mat Skeletonize(this Mat src, Size ksize = default, ElementShape elementShape = ElementShape.Rectangle)
- => src.Skeletonize(out _, ksize, elementShape);
- #endregion
-
- #region Kernel methods
-
- ///
- /// Reduces iterations to 1 and generate a kernel to match the iterations effect
- ///
- ///
- ///
- ///
- public static Mat GetDynamicKernel(ref int iterations, ElementShape elementShape = ElementShape.Ellipse)
- {
- var size = Math.Max(iterations, 1) * 2 + 1;
- iterations = 1;
- return CvInvoke.GetStructuringElement(elementShape, new Size(size, size), new Point(-1, -1));
- }
- #endregion
-
- #region Disposes
- ///
- /// Dispose this if it's a sub matrix / roi
- ///
- /// Mat to dispose
- public static void DisposeIfSubMatrix(this Mat mat)
- {
- if(mat.IsSubmatrix) mat.Dispose();
- }
- #endregion
}
-}
+
+ ///
+ /// Extended OpenCV PutText to accepting line breaks, line alignment and rotation
+ ///
+ public static void PutTextRotated(this Mat src, string text, Point org, FontFace fontFace, double fontScale,
+ MCvScalar color,
+ int thickness = 1, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false,
+ PutTextLineAlignment lineAlignment = default, double angle = 0)
+ => src.PutTextRotated(text, org, fontFace, fontScale, color, thickness, 0, lineType, bottomLeftOrigin,
+ lineAlignment, angle);
+
+ ///
+ /// Extended OpenCV PutText to accepting line breaks, line alignment and rotation
+ ///
+ public static void PutTextRotated(this Mat src, string text, Point org, FontFace fontFace, double fontScale, MCvScalar color,
+ int thickness = 1, int lineGapOffset = 0, LineType lineType = LineType.EightConnected, bool bottomLeftOrigin = false, PutTextLineAlignment lineAlignment = default, double angle = 0)
+ {
+ if (angle % 360 == 0) // No rotation needed, cheaper cycle
+ {
+ src.PutTextExtended(text, org, fontFace, fontScale, color, thickness, lineGapOffset, lineType, bottomLeftOrigin, lineAlignment);
+ return;
+ }
+
+ using var rotatedSrc = src.Clone();
+ rotatedSrc.RotateAdjustBounds(-angle);
+ var sizeDifference = (rotatedSrc.Size - src.Size).Half();
+ org.Offset(sizeDifference.ToPoint());
+ org = org.Rotate(-angle, new Point(rotatedSrc.Size.Width / 2, rotatedSrc.Size.Height / 2));
+ rotatedSrc.PutTextExtended(text, org, fontFace, fontScale, color, thickness, lineGapOffset, lineType, bottomLeftOrigin, lineAlignment);
+
+ using var mask = rotatedSrc.NewBlank();
+ mask.PutTextExtended(text, org, fontFace, fontScale, WhiteColor, thickness, lineGapOffset, lineType, bottomLeftOrigin, lineAlignment);
+
+ rotatedSrc.Rotate(angle, src.Size);
+ mask.Rotate(angle, src.Size);
+
+ rotatedSrc.CopyTo(src, mask);
+ }
+ #endregion
+
+ #region Utilities methods
+
+ ///
+ /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,].
+ /// The function modifies the source image content
+ ///
+ /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content
+ /// Retrieval mode
+ /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation).
+ /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context
+ /// The contour hierarchy
+ public static VectorOfVectorOfPoint FindContours(this IInputOutputArray mat, RetrType mode = RetrType.List, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default)
+ {
+ using var hierarchy = new Mat();
+ var contours = new VectorOfVectorOfPoint();
+ CvInvoke.FindContours(mat, contours, hierarchy, mode, method, offset);
+ return contours;
+ }
+
+ /*
+ ///
+ /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,].
+ /// The function modifies the source image content
+ ///
+ /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content
+ /// Detected contours. Each contour is stored as a vector of points.
+ /// Retrieval mode
+ /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation).
+ /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context
+ /// The contour hierarchy
+ public static int[,] FindContours(this Mat mat, IOutputArray contours, RetrType mode, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default)
+ {
+ using var hierarchy = new Mat();
+ CvInvoke.FindContours(mat, contours, hierarchy, mode, method, offset);
+ var numArray = new int[hierarchy.Cols, 4];
+ var gcHandle = GCHandle.Alloc(numArray, GCHandleType.Pinned);
+ using (var mat2 = new Mat(hierarchy.Rows, hierarchy.Cols, hierarchy.Depth, 4, gcHandle.AddrOfPinnedObject(), hierarchy.Step))
+ hierarchy.CopyTo(mat2);
+ gcHandle.Free();
+ return numArray;
+ }*/
+
+ ///
+ /// Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,].
+ /// The function modifies the source image content
+ ///
+ /// The source 8-bit single channel image. Non-zero pixels are treated as 1s, zero pixels remain 0s - that is image treated as binary. To get such a binary image from grayscale, one may use cvThreshold, cvAdaptiveThreshold or cvCanny. The function modifies the source image content
+ /// The contour hierarchy
+ /// Retrieval mode
+ /// Approximation method (for all the modes, except CV_RETR_RUNS, which uses built-in approximation).
+ /// Offset, by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context
+ /// Detected contours. Each contour is stored as a vector of points.
+ public static VectorOfVectorOfPoint FindContours(this IInputOutputArray mat, out int[,] hierarchy, RetrType mode, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default)
+ {
+ var contours = new VectorOfVectorOfPoint();
+ using var hierarchyMat = new Mat();
+ CvInvoke.FindContours(mat, contours, hierarchyMat, mode, method, offset);
+ hierarchy = new int[hierarchyMat.Cols, 4];
+ var gcHandle = GCHandle.Alloc(hierarchy, GCHandleType.Pinned);
+ using (var mat2 = new Mat(hierarchyMat.Rows, hierarchyMat.Cols, hierarchyMat.Depth, 4, gcHandle.AddrOfPinnedObject(), hierarchyMat.Step))
+ hierarchyMat.CopyTo(mat2);
+ gcHandle.Free();
+ return contours;
+ }
+
+
+ ///
+ /// Determine the area (i.e. total number of pixels in the image),
+ /// initialize the output skeletonized image, and construct the
+ /// morphological structuring element
+ ///
+ ///
+ /// Number of iterations required to perform the skeletoize
+ ///
+ ///
+ public static Mat Skeletonize(this Mat src, out int iterations, Size ksize = default, ElementShape elementShape = ElementShape.Rectangle)
+ {
+ if (ksize.IsEmpty) ksize = new Size(3, 3);
+ Point anchor = new(-1, -1);
+ var skeleton = src.NewBlank();
+ var kernel = Kernel3x3Rectangle;
+
+ var image = src;
+ using var temp = new Mat();
+ iterations = 0;
+ while (true)
+ {
+ iterations++;
+
+ // erode and dilate the image using the structuring element
+ using var eroded = new Mat();
+ CvInvoke.Erode(image, eroded, kernel, anchor, 1, BorderType.Reflect101, default);
+ CvInvoke.Dilate(eroded, temp, kernel, anchor, 1, BorderType.Reflect101, default);
+
+ // subtract the temporary image from the original, eroded
+ // image, then take the bitwise 'or' between the skeleton
+ // and the temporary image
+ CvInvoke.Subtract(image, temp, temp);
+ CvInvoke.BitwiseOr(skeleton, temp, skeleton);
+ image = eroded.Clone();
+
+ // if there are no more 'white' pixels in the image, then
+ // break from the loop
+ if (CvInvoke.CountNonZero(image) == 0) break;
+ }
+
+ return skeleton;
+ }
+
+ ///
+ /// Determine the area (i.e. total number of pixels in the image),
+ /// initialize the output skeletonized image, and construct the
+ /// morphological structuring element
+ ///
+ ///
+ ///
+ ///
+ public static Mat Skeletonize(this Mat src, Size ksize = default, ElementShape elementShape = ElementShape.Rectangle)
+ => src.Skeletonize(out _, ksize, elementShape);
+ #endregion
+
+ #region Kernel methods
+
+ ///
+ /// Reduces iterations to 1 and generate a kernel to match the iterations effect
+ ///
+ ///
+ ///
+ ///
+ public static Mat GetDynamicKernel(ref int iterations, ElementShape elementShape = ElementShape.Ellipse)
+ {
+ var size = Math.Max(iterations, 1) * 2 + 1;
+ iterations = 1;
+ return CvInvoke.GetStructuringElement(elementShape, new Size(size, size), new Point(-1, -1));
+ }
+ #endregion
+
+ #region Disposes
+ ///
+ /// Dispose this if it's a sub matrix / roi
+ ///
+ /// Mat to dispose
+ public static void DisposeIfSubMatrix(this Mat mat)
+ {
+ if(mat.IsSubmatrix) mat.Dispose();
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/EnumExtensions.cs b/UVtools.Core/Extensions/EnumExtensions.cs
index 6847164..0d81996 100644
--- a/UVtools.Core/Extensions/EnumExtensions.cs
+++ b/UVtools.Core/Extensions/EnumExtensions.cs
@@ -12,28 +12,29 @@ using System.Globalization;
using System.Linq;
using UVtools.Core.Objects;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class EnumExtensions
{
- public static class EnumExtensions
+ public static string GetDescription(this Enum value)
{
- public static string GetDescription(this Enum value)
+ var attributes = value.GetType().GetField(value.ToString())?.GetCustomAttributes(typeof(DescriptionAttribute), false);
+ if (attributes is not null && attributes.Any())
{
- var attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
- if (attributes.Any())
- return (attributes.First() as DescriptionAttribute)?.Description;
-
- // If no description is found, the least we can do is replace underscores with spaces
- // You can add your own custom default formatting logic here
- var ti = CultureInfo.CurrentCulture.TextInfo;
- return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
+ if (attributes.First() is DescriptionAttribute attr) return attr.Description;
}
- public static IEnumerable GetAllValuesAndDescriptions(Type t)
- {
- if (!t.IsEnum)
- throw new ArgumentException($"{nameof(t)} must be an enum type");
-
- return Enum.GetValues(t).Cast().OrderBy(e => e).Select(e => new ValueDescription(e, e.GetDescription())).ToList();
- }
+ // If no description is found, the least we can do is replace underscores with spaces
+ // You can add your own custom default formatting logic here
+ var ti = CultureInfo.CurrentCulture.TextInfo;
+ return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
}
-}
+
+ public static IEnumerable GetAllValuesAndDescriptions(Type t)
+ {
+ if (!t.IsEnum)
+ throw new ArgumentException($"{nameof(t)} must be an enum type");
+
+ return Enum.GetValues(t).Cast().OrderBy(e => e).Select(e => new ValueDescription(e, e.GetDescription())).ToList();
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/FileStreamExtensions.cs b/UVtools.Core/Extensions/FileStreamExtensions.cs
index 9d2a130..12fb0d8 100644
--- a/UVtools.Core/Extensions/FileStreamExtensions.cs
+++ b/UVtools.Core/Extensions/FileStreamExtensions.cs
@@ -10,164 +10,163 @@ using System;
using System.IO;
using System.Text;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class FileStreamExtensions
{
- public static class FileStreamExtensions
+ public static uint ReadBytes(this FileStream fs, byte[] bytes, int offset = 0)
{
- public static uint ReadBytes(this FileStream fs, byte[] bytes, int offset = 0)
- {
- return (uint)fs.Read(bytes, offset, bytes.Length);
- }
-
- public static byte[] ReadBytes(this FileStream fs, int length, int offset = 0)
- {
- var buffer = new byte[length];
- fs.Read(buffer, offset, length);
- return buffer;
- }
-
- public static byte[] ReadBytes(this FileStream fs, uint length, int offset = 0)
- => fs.ReadBytes((int)length, offset);
-
- ///
- /// Read from current position to the end of the file
- ///
- ///
- ///
- public static byte[] ReadToEnd(this FileStream fs)
- {
- return fs.ReadBytes((uint)(fs.Length - fs.Position));
- }
-
- public static uint ReadUShortLittleEndian(this FileStream fs, int offset = 0)
- {
- return BitExtensions.ToUShortLittleEndian(fs.ReadBytes(2, offset));
- }
-
- public static uint ReadUShortBigEndian(this FileStream fs, int offset = 0)
- {
- return BitExtensions.ToUShortBigEndian(fs.ReadBytes(2, offset));
- }
-
- public static uint ReadUIntLittleEndian(this FileStream fs, int offset = 0)
- {
- return BitExtensions.ToUIntLittleEndian(fs.ReadBytes(4, offset));
- }
-
- public static uint ReadUIntBigEndian(this FileStream fs, int offset = 0)
- {
- return BitExtensions.ToUIntBigEndian(fs.ReadBytes(4, offset));
- }
-
- public static void WriteUShortLittleEndian(this FileStream fs, ushort value, int offset = 0)
- {
- fs.WriteBytes(BitExtensions.ToBytesLittleEndian(value), offset);
- }
-
- public static void WriteUShortBigEndian(this FileStream fs, ushort value, int offset = 0)
- {
- fs.WriteBytes(BitExtensions.ToBytesBigEndian(value), offset);
- }
-
- public static void WriteUIntLittleEndian(this FileStream fs, uint value, int offset = 0)
- {
- fs.WriteBytes(BitExtensions.ToBytesLittleEndian(value), offset);
- }
-
- public static void WriteUIntBigEndian(this FileStream fs, uint value, int offset = 0)
- {
- fs.WriteBytes(BitExtensions.ToBytesBigEndian(value), offset);
- }
-
- public static void WriteFloatLittleEndian(this FileStream fs, float value, int offset = 0)
- {
- var bytes = BitConverter.GetBytes(value);
- if (!BitConverter.IsLittleEndian) Array.Reverse(bytes); //reverse it so we get little endian.
- fs.WriteBytes(BitConverter.GetBytes(value), offset);
- }
- public static void WriteFloatBigEndian(this FileStream fs, float value, int offset = 0)
- {
- var bytes = BitConverter.GetBytes(value);
- if (BitConverter.IsLittleEndian) Array.Reverse(bytes); //reverse it so we get big endian.
- fs.WriteBytes(BitConverter.GetBytes(value), offset);
- }
-
- 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;
- }
-
- public static uint WriteBytes(this Stream stream, byte[] bytes, int offset = 0)
- {
- stream.Write(bytes, offset, bytes.Length);
- return (uint)bytes.Length;
- }
-
- public static uint WriteString(this FileStream fs, string text, int offset = 0)
- => fs.WriteString(text, Encoding.UTF8, offset);
-
- public static uint WriteString(this FileStream fs, string text, Encoding encoding, int offset = 0)
- {
- var bytes = encoding.GetBytes(text);
- fs.WriteBytes(bytes, offset);
- return (uint)bytes.Length;
- }
-
- public static uint WriteLine(this FileStream fs) => fs.WriteString(Environment.NewLine);
-
- public static uint WriteLine(this FileStream fs, string text, int offset = 0)
- => fs.WriteLine(text, Encoding.UTF8, offset);
-
- public static uint WriteLine(this FileStream fs, string text, Encoding encoding, int offset = 0)
- {
- var bytes = encoding.GetBytes($"{text}{Environment.NewLine}");
- fs.WriteBytes(bytes, offset);
- return (uint)bytes.Length;
- }
-
- public static uint WriteLineLF(this FileStream fs)
- {
- fs.WriteByte(0x0A);
- return 1;
- }
-
- public static uint WriteLineLF(this FileStream fs, string text, int offset = 0)
- => fs.WriteLineLF(text, Encoding.UTF8, offset);
-
- public static uint WriteLineLF(this FileStream fs, string text, Encoding encoding, int offset = 0)
- {
- var bytes = encoding.GetBytes($"{text}\n");
- fs.WriteBytes(bytes, offset);
- return (uint)bytes.Length;
- }
-
- public static uint WriteSerialize(this FileStream fs, object data, int offset = 0)
- {
- return Helpers.SerializeWriteFileStream(fs, data, offset);
- }
-
- ///
- /// Seek to a position, do work and rewind to initial position
- ///
- ///
- ///
- ///
- ///
- public static void SeekDoWorkAndRewind(this FileStream fs, long offset, Action action)
- => fs.SeekDoWorkAndRewind(offset, SeekOrigin.Begin, action);
-
- public static void SeekDoWorkAndRewind(this FileStream fs, long offset, SeekOrigin seekOrigin, Action action)
- {
- var currentPos = fs.Position;
- fs.Seek(offset, seekOrigin); // Go to
- action.Invoke(); // Do work
- fs.Seek(currentPos, SeekOrigin.Begin); // Rewind
- }
+ return (uint)fs.Read(bytes, offset, bytes.Length);
}
-}
+
+ public static byte[] ReadBytes(this FileStream fs, int length, int offset = 0)
+ {
+ var buffer = new byte[length];
+ fs.Read(buffer, offset, length);
+ return buffer;
+ }
+
+ public static byte[] ReadBytes(this FileStream fs, uint length, int offset = 0)
+ => fs.ReadBytes((int)length, offset);
+
+ ///
+ /// Read from current position to the end of the file
+ ///
+ ///
+ ///
+ public static byte[] ReadToEnd(this FileStream fs)
+ {
+ return fs.ReadBytes((uint)(fs.Length - fs.Position));
+ }
+
+ public static uint ReadUShortLittleEndian(this FileStream fs, int offset = 0)
+ {
+ return BitExtensions.ToUShortLittleEndian(fs.ReadBytes(2, offset));
+ }
+
+ public static uint ReadUShortBigEndian(this FileStream fs, int offset = 0)
+ {
+ return BitExtensions.ToUShortBigEndian(fs.ReadBytes(2, offset));
+ }
+
+ public static uint ReadUIntLittleEndian(this FileStream fs, int offset = 0)
+ {
+ return BitExtensions.ToUIntLittleEndian(fs.ReadBytes(4, offset));
+ }
+
+ public static uint ReadUIntBigEndian(this FileStream fs, int offset = 0)
+ {
+ return BitExtensions.ToUIntBigEndian(fs.ReadBytes(4, offset));
+ }
+
+ public static void WriteUShortLittleEndian(this FileStream fs, ushort value, int offset = 0)
+ {
+ fs.WriteBytes(BitExtensions.ToBytesLittleEndian(value), offset);
+ }
+
+ public static void WriteUShortBigEndian(this FileStream fs, ushort value, int offset = 0)
+ {
+ fs.WriteBytes(BitExtensions.ToBytesBigEndian(value), offset);
+ }
+
+ public static void WriteUIntLittleEndian(this FileStream fs, uint value, int offset = 0)
+ {
+ fs.WriteBytes(BitExtensions.ToBytesLittleEndian(value), offset);
+ }
+
+ public static void WriteUIntBigEndian(this FileStream fs, uint value, int offset = 0)
+ {
+ fs.WriteBytes(BitExtensions.ToBytesBigEndian(value), offset);
+ }
+
+ public static void WriteFloatLittleEndian(this FileStream fs, float value, int offset = 0)
+ {
+ var bytes = BitConverter.GetBytes(value);
+ if (!BitConverter.IsLittleEndian) Array.Reverse(bytes); //reverse it so we get little endian.
+ fs.WriteBytes(BitConverter.GetBytes(value), offset);
+ }
+ public static void WriteFloatBigEndian(this FileStream fs, float value, int offset = 0)
+ {
+ var bytes = BitConverter.GetBytes(value);
+ if (BitConverter.IsLittleEndian) Array.Reverse(bytes); //reverse it so we get big endian.
+ fs.WriteBytes(BitConverter.GetBytes(value), offset);
+ }
+
+ 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;
+ }
+
+ public static uint WriteBytes(this Stream stream, byte[] bytes, int offset = 0)
+ {
+ stream.Write(bytes, offset, bytes.Length);
+ return (uint)bytes.Length;
+ }
+
+ public static uint WriteString(this FileStream fs, string text, int offset = 0)
+ => fs.WriteString(text, Encoding.UTF8, offset);
+
+ public static uint WriteString(this FileStream fs, string text, Encoding encoding, int offset = 0)
+ {
+ var bytes = encoding.GetBytes(text);
+ fs.WriteBytes(bytes, offset);
+ return (uint)bytes.Length;
+ }
+
+ public static uint WriteLine(this FileStream fs) => fs.WriteString(Environment.NewLine);
+
+ public static uint WriteLine(this FileStream fs, string text, int offset = 0)
+ => fs.WriteLine(text, Encoding.UTF8, offset);
+
+ public static uint WriteLine(this FileStream fs, string text, Encoding encoding, int offset = 0)
+ {
+ var bytes = encoding.GetBytes($"{text}{Environment.NewLine}");
+ fs.WriteBytes(bytes, offset);
+ return (uint)bytes.Length;
+ }
+
+ public static uint WriteLineLF(this FileStream fs)
+ {
+ fs.WriteByte(0x0A);
+ return 1;
+ }
+
+ public static uint WriteLineLF(this FileStream fs, string text, int offset = 0)
+ => fs.WriteLineLF(text, Encoding.UTF8, offset);
+
+ public static uint WriteLineLF(this FileStream fs, string text, Encoding encoding, int offset = 0)
+ {
+ var bytes = encoding.GetBytes($"{text}\n");
+ fs.WriteBytes(bytes, offset);
+ return (uint)bytes.Length;
+ }
+
+ public static uint WriteSerialize(this FileStream fs, object data, int offset = 0)
+ {
+ return Helpers.SerializeWriteFileStream(fs, data, offset);
+ }
+
+ ///
+ /// Seek to a position, do work and rewind to initial position
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void SeekDoWorkAndRewind(this FileStream fs, long offset, Action action)
+ => fs.SeekDoWorkAndRewind(offset, SeekOrigin.Begin, action);
+
+ public static void SeekDoWorkAndRewind(this FileStream fs, long offset, SeekOrigin seekOrigin, Action action)
+ {
+ var currentPos = fs.Position;
+ fs.Seek(offset, seekOrigin); // Go to
+ action.Invoke(); // Do work
+ fs.Seek(currentPos, SeekOrigin.Begin); // Rewind
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/JsonExtensions.cs b/UVtools.Core/Extensions/JsonExtensions.cs
new file mode 100644
index 0000000..e1d44cb
--- /dev/null
+++ b/UVtools.Core/Extensions/JsonExtensions.cs
@@ -0,0 +1,18 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc.
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+using System.Text.Json;
+
+namespace UVtools.Core.Extensions;
+
+public static class JsonExtensions
+{
+ public static readonly JsonSerializerOptions SettingsIndent = new()
+ {
+ WriteIndented = true
+ };
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/MathExtensions.cs b/UVtools.Core/Extensions/MathExtensions.cs
index 5916926..23ea24a 100644
--- a/UVtools.Core/Extensions/MathExtensions.cs
+++ b/UVtools.Core/Extensions/MathExtensions.cs
@@ -9,150 +9,149 @@
using System;
using System.Globalization;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class MathExtensions
{
- public static class MathExtensions
+ public static byte Clamp(this byte value, byte min, byte max)
{
- public static byte Clamp(this byte value, byte min, byte max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static sbyte Clamp(this sbyte value, sbyte min, sbyte max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static ushort Clamp(this ushort value, ushort min, ushort max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static short Clamp(this short value, short min, short max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static uint Clamp(this uint value, uint min, uint max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static int Clamp(this int value, int min, int max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static ulong Clamp(this ulong value, ulong min, ulong max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static long Clamp(this long value, long min, long max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static float Clamp(this float value, float min, float max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static double Clamp(this double value, double min, double max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static decimal Clamp(this decimal value, decimal min, decimal max)
- {
- return value <= min ? min : value >= max ? max : value;
- }
-
- public static T Clamp(T value, T min, T max) where T : IComparable
- {
- if (value.CompareTo(min) < 0)
- return min;
- if (value.CompareTo(max) > 0)
- return max;
-
- return value;
- }
-
- public static uint DecimalDigits(this float val)
- {
- var valStr = val.ToString(CultureInfo.InvariantCulture).TrimEnd('0');
- if (string.IsNullOrEmpty(valStr)) return 0;
-
- var index = valStr.IndexOf('.');
- if (index < 0) return 0;
-
- return (uint) (valStr[index..].Length - 1);
- }
-
- public static uint DecimalDigits(this double val)
- {
- var valStr = val.ToString(CultureInfo.InvariantCulture).TrimEnd('0');
- if (string.IsNullOrEmpty(valStr)) return 0;
-
- var index = valStr.IndexOf('.');
- if (index < 0) return 0;
-
- return (uint)(valStr[index..].Length - 1);
- }
-
- public static uint DecimalDigits(this decimal val)
- {
- var valStr = val.ToString(CultureInfo.InvariantCulture).TrimEnd('0');
- if (string.IsNullOrEmpty(valStr) || valStr[^1] == '.') return 0;
-
- var index = valStr.IndexOf('.');
- if (index < 0) return 0;
-
- return (uint)(valStr[index..].Length - 1);
- }
-
- public static bool IsInteger(this float val, float tolerance = 0.0001f) => Math.Abs(val - Math.Floor(val)) < tolerance;
- public static bool IsInteger(this double val, double tolerance = 0.0001) => Math.Abs(val - Math.Floor(val)) < tolerance;
- public static bool IsInteger(this decimal val) => val == Math.Floor(val);
-
- public static int GCD(int a, int b)
- {
- while (a != 0 && b != 0)
- {
- if (a > b)
- a %= b;
- else
- b %= a;
- }
-
- return a | b;
- }
-
- public static uint GCD(uint a, uint b)
- {
- while (a != 0 && b != 0)
- {
- if (a > b)
- a %= b;
- else
- b %= a;
- }
-
- return a | b;
- }
-
- public static ulong GCD(ulong a, ulong b)
- {
- while (a != 0 && b != 0)
- {
- if (a > b)
- a %= b;
- else
- b %= a;
- }
-
- return a | b;
- }
-
+ return value <= min ? min : value >= max ? max : value;
}
-}
+
+ public static sbyte Clamp(this sbyte value, sbyte min, sbyte max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static ushort Clamp(this ushort value, ushort min, ushort max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static short Clamp(this short value, short min, short max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static uint Clamp(this uint value, uint min, uint max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static int Clamp(this int value, int min, int max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static ulong Clamp(this ulong value, ulong min, ulong max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static long Clamp(this long value, long min, long max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static float Clamp(this float value, float min, float max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static double Clamp(this double value, double min, double max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static decimal Clamp(this decimal value, decimal min, decimal max)
+ {
+ return value <= min ? min : value >= max ? max : value;
+ }
+
+ public static T Clamp(T value, T min, T max) where T : IComparable
+ {
+ if (value.CompareTo(min) < 0)
+ return min;
+ if (value.CompareTo(max) > 0)
+ return max;
+
+ return value;
+ }
+
+ public static uint DecimalDigits(this float val)
+ {
+ var valStr = val.ToString(CultureInfo.InvariantCulture).TrimEnd('0');
+ if (string.IsNullOrEmpty(valStr)) return 0;
+
+ var index = valStr.IndexOf('.');
+ if (index < 0) return 0;
+
+ return (uint) (valStr[index..].Length - 1);
+ }
+
+ public static uint DecimalDigits(this double val)
+ {
+ var valStr = val.ToString(CultureInfo.InvariantCulture).TrimEnd('0');
+ if (string.IsNullOrEmpty(valStr)) return 0;
+
+ var index = valStr.IndexOf('.');
+ if (index < 0) return 0;
+
+ return (uint)(valStr[index..].Length - 1);
+ }
+
+ public static uint DecimalDigits(this decimal val)
+ {
+ var valStr = val.ToString(CultureInfo.InvariantCulture).TrimEnd('0');
+ if (string.IsNullOrEmpty(valStr) || valStr[^1] == '.') return 0;
+
+ var index = valStr.IndexOf('.');
+ if (index < 0) return 0;
+
+ return (uint)(valStr[index..].Length - 1);
+ }
+
+ public static bool IsInteger(this float val, float tolerance = 0.0001f) => Math.Abs(val - Math.Floor(val)) < tolerance;
+ public static bool IsInteger(this double val, double tolerance = 0.0001) => Math.Abs(val - Math.Floor(val)) < tolerance;
+ public static bool IsInteger(this decimal val) => val == Math.Floor(val);
+
+ public static int GCD(int a, int b)
+ {
+ while (a != 0 && b != 0)
+ {
+ if (a > b)
+ a %= b;
+ else
+ b %= a;
+ }
+
+ return a | b;
+ }
+
+ public static uint GCD(uint a, uint b)
+ {
+ while (a != 0 && b != 0)
+ {
+ if (a > b)
+ a %= b;
+ else
+ b %= a;
+ }
+
+ return a | b;
+ }
+
+ public static ulong GCD(ulong a, ulong b)
+ {
+ while (a != 0 && b != 0)
+ {
+ if (a > b)
+ a %= b;
+ else
+ b %= a;
+ }
+
+ return a | b;
+ }
+
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/NetworkExtensions.cs b/UVtools.Core/Extensions/NetworkExtensions.cs
index d508705..a09067e 100644
--- a/UVtools.Core/Extensions/NetworkExtensions.cs
+++ b/UVtools.Core/Extensions/NetworkExtensions.cs
@@ -13,50 +13,49 @@ using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class NetworkExtensions
{
- public static class NetworkExtensions
+ public static readonly HttpClient HttpClient = new()
{
- public static readonly HttpClient HttpClient = new()
- {
- DefaultRequestHeaders = { UserAgent = { new ProductInfoHeaderValue(About.Software, About.VersionStr) } }
- };
+ DefaultRequestHeaders = { UserAgent = { new ProductInfoHeaderValue(About.Software, About.VersionStr) } }
+ };
- public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<(long total, long bytes)> progress = null, CancellationToken cancellationToken = default)
- {
- // Get the http headers first to examine the content length
- var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
- var contentLength = response.Content.Headers.ContentLength;
+ public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<(long total, long bytes)>? progress = null, CancellationToken cancellationToken = default)
+ {
+ // Get the http headers first to examine the content length
+ var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
+ var contentLength = response.Content.Headers.ContentLength;
- await using var download = await response.Content.ReadAsStreamAsync(cancellationToken);
- // Ignore progress reporting when no progress reporter was
- // passed or when the content length is unknown
- if (progress is null || !contentLength.HasValue)
- {
- await download.CopyToAsync(destination, cancellationToken);
- return response;
- }
-
- // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
- var relativeProgress = new Progress(downloadedBytes => progress.Report(new (contentLength.Value, downloadedBytes)));
- // Use extension method to report progress while downloading
- await download.CopyToAsync(destination, relativeProgress, cancellationToken);
- progress.Report(new(contentLength.Value, contentLength.Value));
+ await using var download = await response.Content.ReadAsStreamAsync(cancellationToken);
+ // Ignore progress reporting when no progress reporter was
+ // passed or when the content length is unknown
+ if (progress is null || !contentLength.HasValue)
+ {
+ await download.CopyToAsync(destination, cancellationToken);
return response;
}
-
- public static async Task DownloadAsync(this HttpClient client, string requestUri, string destinationFilePath, IProgress<(long total, long bytes)> progress = null, CancellationToken cancellationToken = default)
- {
- await using var stream = File.Open(destinationFilePath, FileMode.Create, FileAccess.Write);
- return await DownloadAsync(client, requestUri, stream, progress, cancellationToken);
- }
-
-
- public static async Task DownloadAsync(string requestUri, Stream destination, IProgress<(long total, long bytes)> progress = null, CancellationToken cancellationToken = default)
- => await HttpClient.DownloadAsync(requestUri, destination, progress, cancellationToken);
-
- public static async Task DownloadAsync(string requestUri, string destinationFilePath, IProgress<(long total, long bytes)> progress = null, CancellationToken cancellationToken = default)
- => await HttpClient.DownloadAsync(requestUri, destinationFilePath, progress, cancellationToken);
-
+
+ // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
+ var relativeProgress = new Progress(downloadedBytes => progress.Report(new (contentLength.Value, downloadedBytes)));
+ // Use extension method to report progress while downloading
+ await download.CopyToAsync(destination, relativeProgress, cancellationToken);
+ progress.Report(new(contentLength.Value, contentLength.Value));
+ return response;
}
-}
+
+ public static async Task DownloadAsync(this HttpClient client, string requestUri, string destinationFilePath, IProgress<(long total, long bytes)>? progress = null, CancellationToken cancellationToken = default)
+ {
+ await using var stream = File.Open(destinationFilePath, FileMode.Create, FileAccess.Write);
+ return await DownloadAsync(client, requestUri, stream, progress, cancellationToken);
+ }
+
+
+ public static async Task DownloadAsync(string requestUri, Stream destination, IProgress<(long total, long bytes)>? progress = null, CancellationToken cancellationToken = default)
+ => await HttpClient.DownloadAsync(requestUri, destination, progress, cancellationToken);
+
+ public static async Task DownloadAsync(string requestUri, string destinationFilePath, IProgress<(long total, long bytes)>? progress = null, CancellationToken cancellationToken = default)
+ => await HttpClient.DownloadAsync(requestUri, destinationFilePath, progress, cancellationToken);
+
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/ParallelExtensions.cs b/UVtools.Core/Extensions/ParallelExtensions.cs
index 2f747ad..0ab9346 100644
--- a/UVtools.Core/Extensions/ParallelExtensions.cs
+++ b/UVtools.Core/Extensions/ParallelExtensions.cs
@@ -11,39 +11,38 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class ParallelExtensions
{
- public static class ParallelExtensions
+ //public static readonly ParallelOptions ParallelSingleThread = new() { MaxDegreeOfParallelism = 1 };
+ public static void ForAllInApproximateOrder(this ParallelQuery source, Action action)
{
- //public static readonly ParallelOptions ParallelSingleThread = new() { MaxDegreeOfParallelism = 1 };
- public static void ForAllInApproximateOrder(this ParallelQuery source, Action action)
- {
- Partitioner.Create(source)
- .AsParallel()
- .AsOrdered()
- .ForAll(action);
- }
-
- public static void ForEachInApproximateOrder(this ParallelQuery source, Action action)
- {
-
- source = Partitioner.Create(source)
- .AsParallel()
- .AsOrdered();
-
- Parallel.ForEach(source, action);
-
- }
-
- public static IEnumerable OrderedParallel(this IEnumerable list, Func action)
- {
- var unorderedResult = new ConcurrentBag<(long, T1)>();
- Parallel.ForEach(list, (o, state, i) =>
- {
- unorderedResult.Add((i, action.Invoke(o)));
- });
- var ordered = unorderedResult.OrderBy(o => o.Item1);
- return ordered.Select(o => o.Item2);
- }
+ Partitioner.Create(source)
+ .AsParallel()
+ .AsOrdered()
+ .ForAll(action);
}
-}
+
+ public static void ForEachInApproximateOrder(this ParallelQuery source, Action action)
+ {
+
+ source = Partitioner.Create(source)
+ .AsParallel()
+ .AsOrdered();
+
+ Parallel.ForEach(source, action);
+
+ }
+
+ public static IEnumerable OrderedParallel(this IEnumerable list, Func action)
+ {
+ var unorderedResult = new ConcurrentBag<(long, T1)>();
+ Parallel.ForEach(list, (o, state, i) =>
+ {
+ unorderedResult.Add((i, action.Invoke(o)));
+ });
+ var ordered = unorderedResult.OrderBy(o => o.Item1);
+ return ordered.Select(o => o.Item2);
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/PathExtensions.cs b/UVtools.Core/Extensions/PathExtensions.cs
index 3fff87b..3a7b970 100644
--- a/UVtools.Core/Extensions/PathExtensions.cs
+++ b/UVtools.Core/Extensions/PathExtensions.cs
@@ -9,51 +9,50 @@ using System;
using System.Collections.Generic;
using System.IO;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class PathExtensions
{
- public static class PathExtensions
+ public static string GetFileNameStripAllExtensions(string path)
{
- public static string GetFileNameStripAllExtensions(string path)
- {
- path = Path.GetFileName(path);
- if(string.IsNullOrEmpty(path)) return string.Empty;
- var splitPath = path.Split('.', 2, StringSplitOptions.TrimEntries);
- return splitPath.Length == 0 ? string.Empty : splitPath[0];
- }
-
- public static string GetFileNameStripExtensions(string path, List extensions, out string strippedExtension)
- {
- strippedExtension = string.Empty;
- path = Path.GetFileName(path);
- if (string.IsNullOrEmpty(path)) return string.Empty;
- foreach (var extension in extensions)
- {
- var dotExtension = $".{extension}";
- if (!path.EndsWith(dotExtension, StringComparison.OrdinalIgnoreCase)) continue;
- strippedExtension = extension;
- return path.Remove(path.Length - dotExtension.Length);
- }
-
- return path;
- }
-
- public static string GetTempFilePathWithFilename(string fileName)
- {
- var path = Path.GetTempPath();
- return Path.Combine(path, fileName);
- }
-
- ///
- /// Gets a temporary file with a extension and a prepend string
- ///
- /// Extension name without the dot (.)
- /// Optional prepend file name
- ///
- public static string GetTempFilePathWithExtension(string extension, string prepend = null)
- {
- var path = Path.GetTempPath();
- var fileName = $"{prepend}{Guid.NewGuid()}.{extension}";
- return Path.Combine(path, fileName);
- }
+ path = Path.GetFileName(path);
+ if(string.IsNullOrEmpty(path)) return string.Empty;
+ var splitPath = path.Split('.', 2, StringSplitOptions.TrimEntries);
+ return splitPath.Length == 0 ? string.Empty : splitPath[0];
}
-}
+
+ public static string GetFileNameStripExtensions(string path, List extensions, out string strippedExtension)
+ {
+ strippedExtension = string.Empty;
+ path = Path.GetFileName(path);
+ if (string.IsNullOrEmpty(path)) return string.Empty;
+ foreach (var extension in extensions)
+ {
+ var dotExtension = $".{extension}";
+ if (!path.EndsWith(dotExtension, StringComparison.OrdinalIgnoreCase)) continue;
+ strippedExtension = extension;
+ return path.Remove(path.Length - dotExtension.Length);
+ }
+
+ return path;
+ }
+
+ public static string GetTempFilePathWithFilename(string fileName)
+ {
+ var path = Path.GetTempPath();
+ return Path.Combine(path, fileName);
+ }
+
+ ///
+ /// Gets a temporary file with a extension and a prepend string
+ ///
+ /// Extension name without the dot (.)
+ /// Optional prepend file name
+ ///
+ public static string GetTempFilePathWithExtension(string extension, string? prepend = null)
+ {
+ var path = Path.GetTempPath();
+ var fileName = $"{prepend}{Guid.NewGuid()}.{extension}";
+ return Path.Combine(path, fileName);
+ }
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/PointExtensions.cs b/UVtools.Core/Extensions/PointExtensions.cs
index d95572e..01c6c54 100644
--- a/UVtools.Core/Extensions/PointExtensions.cs
+++ b/UVtools.Core/Extensions/PointExtensions.cs
@@ -8,76 +8,75 @@
using System;
using System.Drawing;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class PointExtensions
{
- public static class PointExtensions
+ public static double FindLength(Point start, Point end) => Math.Sqrt(Math.Pow(end.Y - start.Y, 2) + Math.Pow(end.X - start.X, 2));
+
+ public static bool IsAnyNegative(this Point point) => point.X < 0 || point.Y < 0;
+ public static bool IsBothNegative(this Point point) => point.X < 0 && point.Y < 0;
+ public static bool IsBothZeroOrPositive(this Point point) => point.X >= 0 && point.Y >= 0;
+
+ public static Point Rotate(this Point point, double angleDegree, Point pivot = default)
{
- public static double FindLength(Point start, Point end) => Math.Sqrt(Math.Pow(end.Y - start.Y, 2) + Math.Pow(end.X - start.X, 2));
+ if (angleDegree % 360 == 0) return point;
+ double angle = angleDegree * Math.PI / 180;
+ double cos = Math.Cos(angle);
+ double sin = Math.Sin(angle);
+ int dx = point.X - pivot.X;
+ int dy = point.Y - pivot.Y;
+ double x = cos * dx - sin * dy + pivot.X;
+ double y = sin * dx + cos * dy + pivot.Y;
- public static bool IsAnyNegative(this Point point) => point.X < 0 || point.Y < 0;
- public static bool IsBothNegative(this Point point) => point.X < 0 && point.Y < 0;
- public static bool IsBothZeroOrPositive(this Point point) => point.X >= 0 && point.Y >= 0;
+ return new((int)Math.Round(x), (int)Math.Round(y));
+ }
- public static Point Rotate(this Point point, double angleDegree, Point pivot = default)
+ public static PointF Rotate(this PointF point, double angleDegree, PointF pivot = default)
+ {
+ if (angleDegree % 360 == 0) return point;
+ double angle = angleDegree * Math.PI / 180;
+ double cos = Math.Cos(angle);
+ double sin = Math.Sin(angle);
+ double dx = point.X - pivot.X;
+ double dy = point.Y - pivot.Y;
+ double x = cos * dx - sin * dy + pivot.X;
+ double y = sin * dx + cos * dy + pivot.Y;
+
+ return new((float) x, (float) y);
+ }
+
+ public static void Rotate(Point[] points, double angleDegree, Point pivot = default)
+ {
+ for (int i = 0; i < points.Length; i++)
{
- if (angleDegree % 360 == 0) return point;
- double angle = angleDegree * Math.PI / 180;
- double cos = Math.Cos(angle);
- double sin = Math.Sin(angle);
- int dx = point.X - pivot.X;
- int dy = point.Y - pivot.Y;
- double x = cos * dx - sin * dy + pivot.X;
- double y = sin * dx + cos * dy + pivot.Y;
-
- return new((int)Math.Round(x), (int)Math.Round(y));
+ points[i] = points[i].Rotate(angleDegree, pivot);
}
+ }
- public static PointF Rotate(this PointF point, double angleDegree, PointF pivot = default)
+ public static void Rotate(PointF[] points, double angleDegree, PointF pivot = default)
+ {
+ for (int i = 0; i < points.Length; i++)
{
- if (angleDegree % 360 == 0) return point;
- double angle = angleDegree * Math.PI / 180;
- double cos = Math.Cos(angle);
- double sin = Math.Sin(angle);
- double dx = point.X - pivot.X;
- double dy = point.Y - pivot.Y;
- double x = cos * dx - sin * dy + pivot.X;
- double y = sin * dx + cos * dy + pivot.Y;
-
- return new((float) x, (float) y);
+ points[i] = points[i].Rotate(angleDegree, pivot);
}
+ }
- public static void Rotate(Point[] points, double angleDegree, Point pivot = default)
- {
- for (int i = 0; i < points.Length; i++)
- {
- points[i] = points[i].Rotate(angleDegree, pivot);
- }
- }
-
- public static void Rotate(PointF[] points, double angleDegree, PointF pivot = default)
- {
- for (int i = 0; i < points.Length; i++)
- {
- points[i] = points[i].Rotate(angleDegree, pivot);
- }
- }
-
- public static Point OffsetBy(this Point point, int value)=> new(point.X + value, point.Y + value);
- public static Point OffsetBy(this Point point, int x, int y) => new(point.X + x, point.Y + y);
- public static Point OffsetBy(this Point point, Point other) => new(point.X + other.X, point.Y + other.Y);
+ public static Point OffsetBy(this Point point, int value)=> new(point.X + value, point.Y + value);
+ public static Point OffsetBy(this Point point, int x, int y) => new(point.X + x, point.Y + y);
+ public static Point OffsetBy(this Point point, Point other) => new(point.X + other.X, point.Y + other.Y);
- public static Point Half(this Point point) => new(point.X / 2, point.Y / 2);
- public static PointF Half(this PointF point) => new(point.X / 2, point.Y / 2);
- public static Point ToPoint(this PointF point) => new((int) Math.Round(point.X), (int) Math.Round(point.Y));
+ public static Point Half(this Point point) => new(point.X / 2, point.Y / 2);
+ public static PointF Half(this PointF point) => new(point.X / 2, point.Y / 2);
+ public static Point ToPoint(this PointF point) => new((int) Math.Round(point.X), (int) Math.Round(point.Y));
- public static Size ToSize(this Point point) => new(point.X, point.Y);
+ public static Size ToSize(this Point point) => new(point.X, point.Y);
- public static SizeF ToSize(this PointF point) => new(point.X, point.Y);
+ public static SizeF ToSize(this PointF point) => new(point.X, point.Y);
- }
-}
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/RectangleExtensions.cs b/UVtools.Core/Extensions/RectangleExtensions.cs
index dbdd653..7678488 100644
--- a/UVtools.Core/Extensions/RectangleExtensions.cs
+++ b/UVtools.Core/Extensions/RectangleExtensions.cs
@@ -7,14 +7,13 @@
*/
using System.Drawing;
-namespace UVtools.Core.Extensions
-{
- public static class RectangleExtensions
- {
- public static Point Center(this Rectangle src)
- {
- return new Point(src.Left + src.Width / 2, src.Top + src.Height / 2);
- }
+namespace UVtools.Core.Extensions;
+public static class RectangleExtensions
+{
+ public static Point Center(this Rectangle src)
+ {
+ return new Point(src.Left + src.Width / 2, src.Top + src.Height / 2);
}
-}
+
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/SizeExtensions.cs b/UVtools.Core/Extensions/SizeExtensions.cs
index bf600ff..2429186 100644
--- a/UVtools.Core/Extensions/SizeExtensions.cs
+++ b/UVtools.Core/Extensions/SizeExtensions.cs
@@ -8,118 +8,117 @@
using System;
using System.Drawing;
-namespace UVtools.Core.Extensions
+namespace UVtools.Core.Extensions;
+
+public static class SizeExtensions
{
- public static class SizeExtensions
+ public static readonly string[] SizeSuffixes =
+ { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
+
+ public static string SizeSuffix(long value, byte decimalPlaces = 2)
{
- public static readonly string[] SizeSuffixes =
- { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
+ //if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
+ if (value < 0) { return "-" + SizeSuffix(-value); }
+ if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }
- public static string SizeSuffix(long value, byte decimalPlaces = 2)
+ // mag is 0 for bytes, 1 for KB, 2, for MB, etc.
+ int mag = (int)Math.Log(value, 1024);
+
+ // 1L << (mag * 10) == 2 ^ (10 * mag)
+ // [i.e. the number of bytes in the unit corresponding to mag]
+ decimal adjustedSize = (decimal)value / (1L << (mag * 10));
+
+ // make adjustment when the value is large enough that
+ // it would round up to 1000 or more
+ if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
{
- //if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
- if (value < 0) { return "-" + SizeSuffix(-value); }
- if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }
-
- // mag is 0 for bytes, 1 for KB, 2, for MB, etc.
- int mag = (int)Math.Log(value, 1024);
-
- // 1L << (mag * 10) == 2 ^ (10 * mag)
- // [i.e. the number of bytes in the unit corresponding to mag]
- decimal adjustedSize = (decimal)value / (1L << (mag * 10));
-
- // make adjustment when the value is large enough that
- // it would round up to 1000 or more
- if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
- {
- mag += 1;
- adjustedSize /= 1024;
- }
-
- return string.Format("{0:n" + decimalPlaces + "} {1}",
- adjustedSize,
- SizeSuffixes[mag]);
- }
-
- public static Size Add(this Size size, Size otherSize) => new (size.Width + otherSize.Width, size.Height + otherSize.Height);
- public static Size Add(this Size size) => size.Add(size);
- public static Size Add(this Size size, int pixels) => new (size.Width + pixels, size.Height + pixels);
- public static Size Add(this Size size, int width, int height) => new (size.Width + width, size.Height + height);
-
- public static Size Subtract(this Size size, Size otherSize) => new(size.Width - otherSize.Width, size.Height - otherSize.Height);
- public static Size Subtract(this Size size) => size.Subtract(size);
- public static Size Subtract(this Size size, int pixels) => new(size.Width - pixels, size.Height - pixels);
- public static Size Subtract(this Size size, int width, int height) => new(size.Width - width, size.Height - height);
-
- public static Size Multiply(this Size size, SizeF otherSize) => new((int)(size.Width * otherSize.Width), (int)(size.Height * otherSize.Height));
- public static Size Multiply(this Size size) => size.Multiply(size);
- public static Size Multiply(this Size size, double dxy) => new((int)(size.Width * dxy), (int)(size.Height * dxy));
- public static Size Multiply(this Size size, double dx, double dy) => new((int)(size.Width * dx), (int)(size.Height * dy));
-
- public static Size Divide(this Size size, SizeF otherSize) => new((int)(size.Width / otherSize.Width), (int)(size.Height / otherSize.Height));
- public static Size Divide(this Size size) => size.Divide(size);
- public static Size Divide(this Size size, double dxy) => new((int)(size.Width / dxy), (int)(size.Height / dxy));
- public static Size Divide(this Size size, double dx, double dy) => new((int)(size.Width / dx), (int)(size.Height / dy));
-
- ///
- /// Gets if this size have a zero value on width or height
- ///
- ///
- ///
- public static bool HaveZero(this Size size) => size.Width <= 0 && size.Height <= 0;
-
- ///
- /// Exchange width with height
- ///
- ///
- ///
- public static Size Exchange(this Size size) => new(size.Height, size.Width);
-
- public static int Area(this Rectangle rect) => rect.Width * rect.Height;
-
- public static int Area(this Size size) => size.Width * size.Height;
-
- public static int Max(this Size size) => Math.Max(size.Width, size.Height);
-
-
- public static float Area(this RectangleF rect, int round = -1) => round >= 0 ? (float) Math.Round(rect.Width * rect.Height, round) : rect.Width * rect.Height;
-
- ///
- /// Gets if this size have a zero value on width or height
- ///
- ///
- ///
- public static bool HaveZero(this SizeF size) => size.Width <= 0 && size.Height <= 0;
-
- public static float Area(this SizeF size, int round = -1) => round >= 0 ? (float)Math.Round(size.Width * size.Height, round) : size.Width * size.Height;
-
- public static float Max(this SizeF size) => Math.Max(size.Width, size.Height);
-
- public static Size Half(this Size size) => new(size.Width / 2, size.Height / 2);
-
- public static SizeF Half(this SizeF size) => new(size.Width / 2f, size.Height / 2f);
-
- public static Point ToPoint(this Size size) => new(size.Width, size.Height);
- public static PointF ToPoint(this SizeF size) => new(size.Width, size.Height);
-
- public static Size Rotate(this Size size, double angleDegree)
- {
- if (angleDegree % 360 == 0) return size;
- var sizeHalf = size.Half();
- double angle = angleDegree * Math.PI / 180;
- double cos = Math.Cos(angle);
- double sin = Math.Sin(angle);
- // var newImgWidth = + (float)(x0 + Math.Abs((x - x0) * Math.Cos(rad)) + Math.Abs((y - y0) * Math.Sin(rad)));
- //var newImgHeight = -(float)(y0 + Math.Abs((x - x0) * Math.Sin(rad)) + Math.Abs((y - y0) * Math.Cos(rad)));
- int dx = size.Width - sizeHalf.Width;
- int dy = size.Height - sizeHalf.Height;
- //double width = sizeHalf.Width + Math.Abs(dx * cos) + Math.Abs(dy * sin);
- //double height = sizeHalf.Height + Math.Abs(dx * sin) + Math.Abs(dy * cos);
- double width = Math.Abs(cos * dx) - Math.Abs(sin * dy) + sizeHalf.Width;
- double height = Math.Abs(sin * dx) + Math.Abs(cos * dy) + sizeHalf.Height;
-
- return new((int)Math.Round(width), (int)Math.Round(height));
+ mag += 1;
+ adjustedSize /= 1024;
}
+ return string.Format("{0:n" + decimalPlaces + "} {1}",
+ adjustedSize,
+ SizeSuffixes[mag]);
}
-}
+
+ public static Size Add(this Size size, Size otherSize) => new (size.Width + otherSize.Width, size.Height + otherSize.Height);
+ public static Size Add(this Size size) => size.Add(size);
+ public static Size Add(this Size size, int pixels) => new (size.Width + pixels, size.Height + pixels);
+ public static Size Add(this Size size, int width, int height) => new (size.Width + width, size.Height + height);
+
+ public static Size Subtract(this Size size, Size otherSize) => new(size.Width - otherSize.Width, size.Height - otherSize.Height);
+ public static Size Subtract(this Size size) => size.Subtract(size);
+ public static Size Subtract(this Size size, int pixels) => new(size.Width - pixels, size.Height - pixels);
+ public static Size Subtract(this Size size, int width, int height) => new(size.Width - width, size.Height - height);
+
+ public static Size Multiply(this Size size, SizeF otherSize) => new((int)(size.Width * otherSize.Width), (int)(size.Height * otherSize.Height));
+ public static Size Multiply(this Size size) => size.Multiply(size);
+ public static Size Multiply(this Size size, double dxy) => new((int)(size.Width * dxy), (int)(size.Height * dxy));
+ public static Size Multiply(this Size size, double dx, double dy) => new((int)(size.Width * dx), (int)(size.Height * dy));
+
+ public static Size Divide(this Size size, SizeF otherSize) => new((int)(size.Width / otherSize.Width), (int)(size.Height / otherSize.Height));
+ public static Size Divide(this Size size) => size.Divide(size);
+ public static Size Divide(this Size size, double dxy) => new((int)(size.Width / dxy), (int)(size.Height / dxy));
+ public static Size Divide(this Size size, double dx, double dy) => new((int)(size.Width / dx), (int)(size.Height / dy));
+
+ ///
+ /// Gets if this size have a zero value on width or height
+ ///
+ ///
+ ///
+ public static bool HaveZero(this Size size) => size.Width <= 0 && size.Height <= 0;
+
+ ///
+ /// Exchange width with height
+ ///
+ ///
+ ///
+ public static Size Exchange(this Size size) => new(size.Height, size.Width);
+
+ public static int Area(this Rectangle rect) => rect.Width * rect.Height;
+
+ public static int Area(this Size size) => size.Width * size.Height;
+
+ public static int Max(this Size size) => Math.Max(size.Width, size.Height);
+
+
+ public static float Area(this RectangleF rect, int round = -1) => round >= 0 ? (float) Math.Round(rect.Width * rect.Height, round) : rect.Width * rect.Height;
+
+ ///
+ /// Gets if this size have a zero value on width or height
+ ///
+ ///
+ ///
+ public static bool HaveZero(this SizeF size) => size.Width <= 0 && size.Height <= 0;
+
+ public static float Area(this SizeF size, int round = -1) => round >= 0 ? (float)Math.Round(size.Width * size.Height, round) : size.Width * size.Height;
+
+ public static float Max(this SizeF size) => Math.Max(size.Width, size.Height);
+
+ public static Size Half(this Size size) => new(size.Width / 2, size.Height / 2);
+
+ public static SizeF Half(this SizeF size) => new(size.Width / 2f, size.Height / 2f);
+
+ public static Point ToPoint(this Size size) => new(size.Width, size.Height);
+ public static PointF ToPoint(this SizeF size) => new(size.Width, size.Height);
+
+ public static Size Rotate(this Size size, double angleDegree)
+ {
+ if (angleDegree % 360 == 0) return size;
+ var sizeHalf = size.Half();
+ double angle = angleDegree * Math.PI / 180;
+ double cos = Math.Cos(angle);
+ double sin = Math.Sin(angle);
+ // var newImgWidth = + (float)(x0 + Math.Abs((x - x0) * Math.Cos(rad)) + Math.Abs((y - y0) * Math.Sin(rad)));
+ //var newImgHeight = -(float)(y0 + Math.Abs((x - x0) * Math.Sin(rad)) + Math.Abs((y - y0) * Math.Cos(rad)));
+ int dx = size.Width - sizeHalf.Width;
+ int dy = size.Height - sizeHalf.Height;
+ //double width = sizeHalf.Width + Math.Abs(dx * cos) + Math.Abs(dy * sin);
+ //double height = sizeHalf.Height + Math.Abs(dx * sin) + Math.Abs(dy * cos);
+ double width = Math.Abs(cos * dx) - Math.Abs(sin * dy) + sizeHalf.Width;
+ double height = Math.Abs(sin * dx) + Math.Abs(cos * dy) + sizeHalf.Height;
+
+ return new((int)Math.Round(width), (int)Math.Round(height));
+ }
+
+}
\ No newline at end of file
diff --git a/UVtools.Core/Extensions/SpanExtensions.cs b/UVtools.Core/Extensions/SpanExtensions.cs
index 204fde1..bf4f3a1 100644
--- a/UVtools.Core/Extensions/SpanExtensions.cs
+++ b/UVtools.Core/Extensions/SpanExtensions.cs
@@ -9,33 +9,32 @@ using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
-namespace UVtools.Core.Extensions
-{
- public static class SpanExtensions
- {
- public static unsafe void Fill(this Span span, Func provider) where T : struct
- {
- int
- cores = Environment.ProcessorCount,
- batch = span.Length / cores,
- mod = span.Length % cores,
- size = Unsafe.SizeOf();
- ref T r0 = ref span.GetPinnableReference();
- fixed (byte* p0 = &Unsafe.As(ref r0))
- {
- byte* p = p0;
- Parallel.For(0, cores, i =>
- {
- byte* pi = p + i * batch * size;
- for (int j = 0; j < batch; j++, pi += size)
- Unsafe.Write(pi, provider());
- });
+namespace UVtools.Core.Extensions;
- // Remaining values
- if (mod < 1) return;
- for (int i = span.Length - mod; i < span.Length; i++)
- Unsafe.Write(p + i * size, provider());
- }
+public static class SpanExtensions
+{
+ public static unsafe void Fill(this Span span, Func