diff --git a/CREDITS.md b/CREDITS.md
index 623eaed..70acfe5 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -31,4 +31,5 @@
* Jeremy Lauzon
* Peter Teal
* Ulrich Mandl
-* Erwin Ried
\ No newline at end of file
+* Erwin Ried
+* Matthias Keila
\ No newline at end of file
diff --git a/UVtools.WPF/Controls/AdvancedImageBox.cs b/UVtools.WPF/Controls/AdvancedImageBox.cs
index 0922927..a3ff991 100644
--- a/UVtools.WPF/Controls/AdvancedImageBox.cs
+++ b/UVtools.WPF/Controls/AdvancedImageBox.cs
@@ -1,26 +1,86 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Diagnostics;
+using System.Drawing;
+using System.Runtime.CompilerServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
-using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Skia;
using Avalonia.Styling;
-using Avalonia.Visuals.Media.Imaging;
-using SkiaSharp;
using UVtools.Core.Extensions;
-
+using UVtools.WPF.Extensions;
+using Bitmap = Avalonia.Media.Imaging.Bitmap;
+using Brushes = Avalonia.Media.Brushes;
+using Pen = Avalonia.Media.Pen;
+using Point = System.Drawing.Point;
+using Size = System.Drawing.Size;
namespace UVtools.WPF.Controls
{
- public class AdvancedImageBox : ScrollViewer, IStyleable
+ public class AdvancedImageBox : ScrollViewer, IStyleable, INotifyPropertyChanged
{
+ #region Bindable Base
+ ///
+ /// Multicast event for property change notifications.
+ ///
+ public event PropertyChangedEventHandler ImageBoxPropertyChanged;
+
+ ///
+ /// Checks if a property already matches a desired value. Sets the property and
+ /// notifies listeners only when necessary.
+ ///
+ /// Type of the property.
+ /// Reference to a property with both getter and setter.
+ /// Desired value for the property.
+ ///
+ /// Name of the property used to notify listeners. This
+ /// value is optional and can be provided automatically when invoked from compilers that
+ /// support CallerMemberName.
+ ///
+ ///
+ /// True if the value was changed, false if the existing value matched the
+ /// desired value.
+ ///
+ protected bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (Equals(storage, value))
+ {
+ return false;
+ }
+
+ storage = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
+
+ protected bool SetProperty([CallerMemberName] string propertyName = null)
+ {
+ OnPropertyChanged(propertyName);
+ return 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 OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ var eventHandler = ImageBoxPropertyChanged;
+ eventHandler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ #endregion
+
#region Sub Classes
///
@@ -377,27 +437,32 @@ namespace UVtools.WPF.Controls
public static readonly int MaxZoom = 3500;
#endregion
- public static StyledProperty GridCellSizeProperty =
- AvaloniaProperty.Register(nameof(GridCellSize), 8, false, BindingMode.TwoWay);
-
///
/// Gets or sets the basic cell size
///
public byte GridCellSize
{
- get => GetValue(GridCellSizeProperty);
- set => SetValue(GridCellSizeProperty, value);
+ get => _gridCellSize;
+ set => SetProperty(ref _gridCellSize, value);
}
///
/// Gets or sets the color used to create the checkerboard style background
///
- public ISolidColorBrush GridColor { get; set; } = Brushes.Gainsboro;
+ public ISolidColorBrush GridColor
+ {
+ get => _gridColor;
+ set => SetProperty(ref _gridColor, value);
+ }
///
/// Gets or sets the color used to create the checkerboard style background
///
- public ISolidColorBrush GridColorAlternate { get; set; } = Brushes.White;
+ public ISolidColorBrush GridColorAlternate
+ {
+ get => _gridColorAlternate;
+ set => SetProperty(ref _gridColorAlternate, value);
+ }
///
/// Gets or sets the image to be displayed
@@ -407,7 +472,7 @@ namespace UVtools.WPF.Controls
get => _image;
set
{
- _image = value;
+ if (!SetProperty(ref _image, value)) return;
if (_image is null)
{
SizedContainer.Width = 0;
@@ -444,15 +509,18 @@ namespace UVtools.WPF.Controls
///
/// Gets or sets if the checkerboard background should be displayed
///
- public bool ShowGrid { get; set; } = true;
+ public bool ShowGrid
+ {
+ get => _showGrid;
+ set => SetProperty(ref _showGrid, value);
+ }
public bool IsPanning
{
get => _isPanning;
protected set
{
- if (_isPanning == value) return;
- _isPanning = value;
+ if (!SetProperty(ref _isPanning, value)) return;
_startScrollPosition = Offset;
if (value)
@@ -472,36 +540,52 @@ namespace UVtools.WPF.Controls
{
get
{
- Rect viewport = GetImageViewPort();
- return new Point(viewport.Width / 2, viewport.Height / 2);
+ Rectangle viewport = GetImageViewPort();
+ return new Point((int) (viewport.Width / 2), (int) (viewport.Height / 2));
}
}
- public bool AutoPan { get; set; } = true;
+ public bool AutoPan
+ {
+ get => _autoPan;
+ set => SetProperty(ref _autoPan, value);
+ }
- public PanMouseButtons PanWithMouseButtons { get; set; } = PanMouseButtons.LeftButton | PanMouseButtons.MiddleButton | PanMouseButtons.RightButton;
+ public PanMouseButtons PanWithMouseButtons
+ {
+ get => _panWithMouseButtons;
+ set => SetProperty(ref _panWithMouseButtons, value);
+ }
- public bool PanWithArrows { get; set; } = true;
+ public bool PanWithArrows
+ {
+ get => _panWithArrows;
+ set => SetProperty(ref _panWithArrows, value);
+ }
- public bool InvertMouse { get; set; } = false;
+ public bool InvertMouse
+ {
+ get => _invertMouse;
+ set => SetProperty(ref _invertMouse, value);
+ }
- public bool AutoCenter { get; set; } = true;
+ public bool AutoCenter
+ {
+ get => _autoCenter;
+ set => SetProperty(ref _autoCenter, value);
+ }
- public SizeModes SizeMode { get; set; } = SizeModes.Normal;
+ public SizeModes SizeMode
+ {
+ get => _sizeMode;
+ set => SetProperty(ref _sizeMode, value);
+ }
private bool _allowZoom = true;
public virtual bool AllowZoom
{
get => _allowZoom;
- set
- {
- if (AllowZoom != value)
- {
- _allowZoom = value;
-
- //this.OnAllowZoomChanged(EventArgs.Empty);
- }
- }
+ set => SetProperty(ref _allowZoom, value);
}
ZoomLevelCollection _zoomLevels = ZoomLevelCollection.Default;
@@ -512,15 +596,7 @@ namespace UVtools.WPF.Controls
public virtual ZoomLevelCollection ZoomLevels
{
get => _zoomLevels;
- set
- {
- if (ZoomLevels != value)
- {
- _zoomLevels = value;
-
- //this.OnZoomLevelsChanged(EventArgs.Empty);
- }
- }
+ set => SetProperty(ref _zoomLevels, value);
}
private int _zoom = 100;
@@ -544,15 +620,7 @@ namespace UVtools.WPF.Controls
public virtual ISolidColorBrush PixelGridColor
{
get => _pixelGridColor;
- set
- {
- if (PixelGridColor != value)
- {
- _pixelGridColor = value;
-
- //this.OnPixelGridColorChanged(EventArgs.Empty);
- }
- }
+ set => SetProperty(ref _pixelGridColor, value);
}
private int _pixelGridThreshold = 5;
@@ -564,15 +632,13 @@ namespace UVtools.WPF.Controls
public virtual int PixelGridThreshold
{
get => _pixelGridThreshold;
- set
- {
- if (PixelGridThreshold != value)
- {
- _pixelGridThreshold = value;
+ set => SetProperty(ref _pixelGridThreshold, value);
+ }
- //this.OnPixelGridThresholdChanged(EventArgs.Empty);
- }
- }
+ public RectangleF SelectionRegion
+ {
+ get => _selectionRegion;
+ set => SetProperty(ref _selectionRegion, value);
}
@@ -583,6 +649,17 @@ namespace UVtools.WPF.Controls
private Vector _startScrollPosition;
private bool _isPanning;
private Bitmap _image;
+ private byte _gridCellSize;
+ private ISolidColorBrush _gridColor = Brushes.Gainsboro;
+ private ISolidColorBrush _gridColorAlternate = Brushes.White;
+ private bool _showGrid = true;
+ private bool _autoPan = true;
+ private PanMouseButtons _panWithMouseButtons = PanMouseButtons.LeftButton | PanMouseButtons.MiddleButton | PanMouseButtons.RightButton;
+ private bool _panWithArrows = true;
+ private bool _invertMouse = false;
+ private bool _autoCenter = true;
+ private SizeModes _sizeMode = SizeModes.Normal;
+ private RectangleF _selectionRegion;
public ContentControl FillContainer { get; } = new ContentControl
{
@@ -609,7 +686,7 @@ namespace UVtools.WPF.Controls
//Container.PointerPressed += ScrollViewerOnPointerPressed;
//Container.PointerReleased += ScrollViewerOnPointerReleased;
- PropertyChanged += OnPropertyChanged;
+ PropertyChanged += EventOnPropertyChanged;
}
private void FillContainerOnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
@@ -624,7 +701,7 @@ namespace UVtools.WPF.Controls
// 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(this));
+ ProcessMouseZoom(e.Delta.Y > 0, e.GetPosition(this).ToDotNet());
}
//InvalidateVisual();
@@ -725,7 +802,7 @@ namespace UVtools.WPF.Controls
/// 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));
+ => IsPointInImage(new Point((int) x, (int) y));
///
/// Converts the given client size point to represent a coordinate on the source image.
@@ -740,9 +817,12 @@ namespace UVtools.WPF.Controls
///
/// 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)
- => PointToImage(x, y, false);
+ public Point PointToImage(double x, double y, bool fitToBounds = false)
+ => PointToImage(x, y, fitToBounds);
///
/// Converts the given client size point to represent a coordinate on the source image.
@@ -753,18 +833,9 @@ namespace UVtools.WPF.Controls
/// 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)
- => 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.
- /// 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)
+ public Point PointToImage(int x, int y, bool fitToBounds = false)
{
- return PointToImage(x, y, false);
+ return PointToImage(x, y, fitToBounds);
}
///
@@ -777,20 +848,20 @@ namespace UVtools.WPF.Controls
/// Point.Empty if the point could not be matched to the source image, otherwise the new translated point
public virtual Point PointToImage(Point point, bool fitToBounds)
{
- double x;
- double y;
+ int x;
+ int 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;
+ x = (int) ((point.X + Offset.X - viewport.X) / ZoomFactor);
+ y = (int) ((point.Y + Offset.Y - viewport.Y) / ZoomFactor);
if (fitToBounds)
{
- x = x.Clamp(0, Image.Size.Width);
- y = y.Clamp(0, Image.Size.Height);
+ x = x.Clamp(0, (int) Image.Size.Width);
+ y = y.Clamp(0, (int) Image.Size.Height);
}
}
else
@@ -810,7 +881,7 @@ namespace UVtools.WPF.Controls
/// 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));
+ => ScrollTo(new Point((int) x, (int) y), new Point((int) relativeX, (int) relativeY));
///
/// Scrolls the control to the given point in the image, offset at the specified display point
@@ -833,9 +904,15 @@ namespace UVtools.WPF.Controls
var y = imageLocation.Y * ZoomFactor - relativeDisplayPoint.Y;
Offset = new Vector(x, y);
- Debug.WriteLine($"{Offset} | " +
- $"{relativeDisplayPoint} | " +
- $"{HorizontalScrollBarValue},{VerticalScrollBarValue}");
+ Debug.WriteLine(
+ $"X/Y: {x},{y} | \n" +
+ $"Offset: {Offset} | \n" +
+ $"ZoomFactor: {ZoomFactor} | \n" +
+ $"Image Location: {imageLocation}\n" +
+ $"MAX: {HorizontalScrollBarMaximum},{VerticalScrollBarMaximum} \n" +
+ $"ViewPort: {Viewport.Width},{Viewport.Height} \n" +
+ $"Container: {SizedContainer.Width},{SizedContainer.Height} \n" +
+ $"Relative: {relativeDisplayPoint}");
}
///
@@ -846,7 +923,7 @@ namespace UVtools.WPF.Controls
/// The source of the zoom operation.
private void SetZoom(int value, ImageZoomActions actions)
{
- var previousZoom = Zoom;
+ var previousZoom = _zoom;
value = value.Clamp(MinZoom, MaxZoom);
if (_zoom != value)
@@ -857,7 +934,7 @@ namespace UVtools.WPF.Controls
InvalidateArrange();
}
-
+ OnPropertyChanged(nameof(Zoom));
//this.OnZoomChanged(EventArgs.Empty);
//this.OnZoomed(new ImageBoxZoomEventArgs(actions, source, previousZoom, this.Zoom));
}
@@ -929,12 +1006,58 @@ namespace UVtools.WPF.Controls
Zoom = (int)Math.Round(Math.Floor(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.
+ public void ZoomToRegion(double x, double y, double width, double height)
+ {
+ ZoomToRegion(new Rectangle((int)x, (int)y, (int)width, (int)height));
+ }
+
+ ///
+ /// 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.
+ public void ZoomToRegion(int x, int y, int width, int height)
+ {
+ ZoomToRegion(new Rectangle(x, y, width, height));
+ }
+
+ ///
+ /// Adjusts the view port to fit the given region
+ ///
+ /// The rectangle to fit the view port to.
+ public virtual void ZoomToRegion(Rectangle rectangle)
+ {
+ var ratioX = Viewport.Width / rectangle.Width;
+ var ratioY = Viewport.Height / rectangle.Height;
+ var zoomFactor = Math.Min(ratioX, ratioY);
+ var cx = rectangle.X + rectangle.Width / 2;
+ var cy = rectangle.Y + rectangle.Height / 2;
+
+ Zoom = (int)(zoomFactor * 100);
+ CenterAt(new Point(cx, cy));
+ }
+
+ ///
+ /// Adjusts the view port to fit the given region
+ ///
+ /// The rectangle to fit the view port to.
+ public virtual void ZoomToRegion(Rect rectangle) => ZoomToRegion(rectangle.ToDotNet());
+
///
/// Centers the given point in the image in the center of the control
///
/// The point of the image to attempt to center.
public virtual void CenterAt(Point imageLocation)
- => ScrollTo(imageLocation, new Point(Viewport.Width / 2, Viewport.Height / 2));
+ => ScrollTo(imageLocation, new Point((int) (Viewport.Width / 2), (int) (Viewport.Height / 2)));
///
/// Centers the given point in the image in the center of the control
@@ -950,14 +1073,14 @@ namespace UVtools.WPF.Controls
/// 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));
+ => CenterAt(new Point((int) x, (int) y));
///
/// Resets the viewport to show the center of the image.
///
public virtual void CenterToImage()
{
- Offset = new Point(HorizontalScrollBarMaximum / 2, VerticalScrollBarMaximum / 2);
+ Offset = new Vector(HorizontalScrollBarMaximum / 2, VerticalScrollBarMaximum / 2);
}
private bool UpdateViewPort()
@@ -984,7 +1107,7 @@ namespace UVtools.WPF.Controls
if (changed)
{
- Debug.WriteLine($"Update ViewPort: {DateTime.Now.Ticks}");
+ //Debug.WriteLine($"Update ViewPort: {DateTime.Now.Ticks}");
InvalidateArrange();
}
@@ -1001,7 +1124,7 @@ namespace UVtools.WPF.Controls
SetZoom(100, ImageZoomActions.ActualSize | (Zoom < 100 ? ImageZoomActions.ZoomIn : ImageZoomActions.ZoomOut));
}
- private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ private void EventOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
//Debug.WriteLine(e.Property.Name);
if (e.Property.Name == nameof(VerticalScrollBarValue) ||
@@ -1021,28 +1144,188 @@ namespace UVtools.WPF.Controls
#region Overrides
- /*public override void EndInit()
- {
- base.EndInit();
- SKPaint SKBrush = new SKPaint { IsAntialias = true, Color = new SKColor(0, 0, 0) };
- SKBrush.Shader = SKShader.CreateColor(SKBrush.Color);
- //RenderTarget = new RenderTargetBitmap(new PixelSize((int)Viewport.Width, (int)Viewport.Height), new Vector(96, 96));
-
- //var context = RenderTarget.CreateDrawingContext(null);
- //SkiaContext = (context as ISkiaDrawingContextImpl);
- //SkiaContext.SkCanvas.Clear(new SKColor(100, 100, 255));
- }*/
#endregion
#region Methods
+ #region Selection
+ ///
+ /// 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 virtual Point GetScaledPoint(Point source)
+ {
+ return new Point((int)(source.X * this.ZoomFactor), (int)(source.Y * this.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 virtual PointF GetScaledPoint(PointF source)
+ {
+ return new PointF((float)(source.X * this.ZoomFactor), (float)(source.Y * this.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 Rectangle GetScaledRectangle(int x, int y, int width, int height)
+ {
+ return GetScaledRectangle(new Rectangle(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 Rectangle GetScaledRectangle(Point location, Size size)
+ {
+ return GetScaledRectangle(new Rectangle(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 virtual Rectangle GetScaledRectangle(Rectangle source)
+ {
+ return new Rectangle((int)(source.Left * ZoomFactor), (int)(source.Top * this.ZoomFactor), (int)(source.Width * this.ZoomFactor), (int)(source.Height * this.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 virtual RectangleF GetScaledRectangle(RectangleF source)
+ {
+ return new RectangleF((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 this.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 this.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 virtual SizeF GetScaledSize(SizeF source)
+ {
+ return new SizeF((float)(source.Width * this.ZoomFactor), (float)(source.Height * this.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 virtual Size GetScaledSize(Size source)
+ {
+ return new Size((int)(source.Width * this.ZoomFactor), (int)(source.Height * this.ZoomFactor));
+ }
+
+ ///
+ /// Creates a selection region which encompasses the entire image
+ ///
+ /// Thrown if no image is currently set
+ public virtual void SelectAll()
+ {
+ if (Image is null) return;
+ SelectionRegion = new RectangleF(0, 0, (float) Image.Size.Width, (float) Image.Size.Height);
+ }
+
+ ///
+ /// Clears any existing selection region
+ ///
+ public virtual void SelectNone()
+ {
+ SelectionRegion = RectangleF.Empty;
+ }
+
+ #endregion
public void LoadImage(string path)
{
Image = new Bitmap(path);
- Image.Save("D:\\test2.png");
//ImageControl.Source = Image;
//ImageControl.InvalidateVisual();
}
@@ -1073,36 +1356,35 @@ namespace UVtools.WPF.Controls
else
{
context.FillRectangle(Background, new Rect(0, 0, Viewport.Width, Viewport.Height));
- //SkiaContext.SkCanvas.Clear(new SKColor(100, 100, 255));
}
if (Image is null) return;
// Draw iamge
context.DrawImage(Image,
- GetSourceImageRegion(),
- GetImageViewPort()
+ GetSourceImageRegion().ToAvalonia(),
+ GetImageViewPort().ToAvalonia()
);
//SkiaContext.SkCanvas.dr
// Draw pixel grid
var pixelSize = ZoomFactor;
if (pixelSize > PixelGridThreshold)
{
- Rect viewport = GetImageViewPort();
+ var viewport = GetImageViewPort();
var offsetX = Offset.X % pixelSize;
var offsetY = Offset.Y % pixelSize;
Pen pen = new Pen(PixelGridColor);
for (double x = viewport.X + pixelSize - offsetX; x < viewport.Right; x += pixelSize)
{
- context.DrawLine(pen, new Point(x, viewport.X), new Point(x, viewport.Bottom));
+ context.DrawLine(pen, new Avalonia.Point(x, viewport.X), new Avalonia.Point(x, viewport.Bottom));
}
for (double y = viewport.Y + pixelSize - offsetY; y < viewport.Bottom; y += pixelSize)
{
- context.DrawLine(pen, new Point(viewport.Y, y), new Point(viewport.Right, y));
+ context.DrawLine(pen, new Avalonia.Point(viewport.Y, y), new Avalonia.Point(viewport.Right, y));
}
- context.DrawRectangle(pen, viewport);
+ context.DrawRectangle(pen, viewport.ToAvalonia());
}
}
@@ -1110,55 +1392,55 @@ namespace UVtools.WPF.Controls
/// Gets the source image region.
///
///
- public virtual Rect GetSourceImageRegion()
+ public virtual Rectangle GetSourceImageRegion()
{
- if (Image is null) return new Rect(0, 0, 0, 0);
+ if (Image is null) return Rectangle.Empty;
if (SizeMode != SizeModes.Stretch)
{
var viewPort = GetImageViewPort();
- var sourceLeft = Offset.X / ZoomFactor;
- var sourceTop = Offset.Y / ZoomFactor;
- var sourceWidth = viewPort.Width / ZoomFactor;
- var sourceHeight = viewPort.Height / ZoomFactor;
+ int sourceLeft = (int) (Offset.X / ZoomFactor);
+ int sourceTop = (int) (Offset.Y / ZoomFactor);
+ int sourceWidth = (int) (viewPort.Width / ZoomFactor);
+ int sourceHeight = (int) (viewPort.Height / ZoomFactor);
- return new Rect(sourceLeft, sourceTop, sourceWidth, sourceHeight);
+ return new Rectangle(sourceLeft, sourceTop, sourceWidth, sourceHeight);
}
- return new Rect(0, 0, Image.Size.Width, Image.Size.Height);
+ return new Rectangle(0, 0, (int) Image.Size.Width, (int) Image.Size.Height);
}
///
/// Gets the image view port.
///
///
- public virtual Rect GetImageViewPort()
+ public virtual Rectangle GetImageViewPort()
{
- if (Viewport.Width == 0 && Viewport.Height == 0) return Rect.Empty;
+ if (Viewport.Width == 0 && Viewport.Height == 0) return Rectangle.Empty;
- double xOffset = 0;
- double yOffset = 0;
- double width;
- double height;
+ int xOffset = 0;
+ int yOffset = 0;
+ int width;
+ int height;
if (SizeMode != SizeModes.Stretch)
{
if (AutoCenter)
{
- xOffset = !IsHorizontalBarVisible ? (Viewport.Width - ScaledImageWidth) / 2 : 0;
- yOffset = !IsVerticalBarVisible ? (Viewport.Height - ScaledImageHeight) / 2 : 0;
+ xOffset = (int) (!IsHorizontalBarVisible ? (Viewport.Width - ScaledImageWidth) / 2 : 0);
+ yOffset = (int) (!IsVerticalBarVisible ? (Viewport.Height - ScaledImageHeight) / 2 : 0);
}
- width = Math.Min(ScaledImageWidth - Math.Abs(Offset.X), Viewport.Width);
- height = Math.Min(ScaledImageHeight - Math.Abs(Offset.Y), Viewport.Height);
+ width = (int) Math.Min(ScaledImageWidth - Math.Abs(Offset.X), Viewport.Width);
+ height = (int) Math.Min(ScaledImageHeight - Math.Abs(Offset.Y), Viewport.Height);
}
else
{
- width = Viewport.Width;
- height = Viewport.Height;
+ width = (int) Viewport.Width;
+ height = (int) Viewport.Height;
}
- return new Rect(xOffset, yOffset, width, height);
+ return new Rectangle(xOffset, yOffset, width, height);
}
///
@@ -1191,7 +1473,7 @@ namespace UVtools.WPF.Controls
if (location.X > Viewport.Width) return;
if (location.Y > Viewport.Height) return;
- _startMousePosition = location;
+ _startMousePosition = location.ToDotNet();
IsPanning = true;
}
@@ -1226,7 +1508,7 @@ namespace UVtools.WPF.Controls
y = (_startScrollPosition.Y - (_startMousePosition.Y - location.Y));
}
- Offset = new Point(x, y);
+ Offset = new Vector(x, y);
e.Handled = true;
}
#endregion
diff --git a/UVtools.WPF/Extensions/DrawingExtensions.cs b/UVtools.WPF/Extensions/DrawingExtensions.cs
index 179d97a..b848c48 100644
--- a/UVtools.WPF/Extensions/DrawingExtensions.cs
+++ b/UVtools.WPF/Extensions/DrawingExtensions.cs
@@ -6,6 +6,9 @@
* of this license document, but changing it is not allowed.
*/
+using System.Drawing;
+using Avalonia;
+
namespace UVtools.WPF.Extensions
{
public static class DrawingExtensions
@@ -19,5 +22,15 @@ namespace UVtools.WPF.Extensions
{
return System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B);
}
+
+ public static Rect ToAvalonia(this Rectangle rectangle)
+ {
+ return new Rect(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
+ }
+
+ public static Rectangle ToDotNet(this Rect rectangle)
+ {
+ return new Rectangle((int) rectangle.X, (int) rectangle.Y, (int) rectangle.Width, (int) rectangle.Height);
+ }
}
}
diff --git a/UVtools.WPF/Extensions/PrimitivesExtensions.cs b/UVtools.WPF/Extensions/PrimitivesExtensions.cs
index 7c99cf4..8a96a33 100644
--- a/UVtools.WPF/Extensions/PrimitivesExtensions.cs
+++ b/UVtools.WPF/Extensions/PrimitivesExtensions.cs
@@ -12,6 +12,11 @@ namespace UVtools.WPF.Extensions
{
public static class PrimitivesExtensions
{
+ public static System.Drawing.Point ToDotNet(this Point point)
+ {
+ return new System.Drawing.Point((int) point.X, (int) point.Y);
+ }
+
public static bool IsEmpty(this Point point)
{
return point.X == 0 && point.Y == 0;
diff --git a/UVtools.WPF/LayerCache.cs b/UVtools.WPF/LayerCache.cs
index fd9f53b..fcec132 100644
--- a/UVtools.WPF/LayerCache.cs
+++ b/UVtools.WPF/LayerCache.cs
@@ -11,20 +11,20 @@ using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
using UVtools.Core;
+using UVtools.Core.Extensions;
namespace UVtools.WPF
{
public sealed class LayerCache
{
private Layer _layer;
- private Mat _image;
private Array _layerHierarchyJagged;
private VectorOfVectorOfPoint _layerContours;
private Mat _layerHierarchy;
public bool IsCached => !ReferenceEquals(_layer, null);
- public Layer Layer
+ public unsafe Layer Layer
{
get => _layer;
set
@@ -32,15 +32,21 @@ namespace UVtools.WPF
//if (ReferenceEquals(_layer, value)) return;
Clear();
_layer = value;
- _image = _layer.LayerMat;
+ Image = _layer.LayerMat;
ImageBgr = new Mat();
- CvInvoke.CvtColor(_image, ImageBgr, ColorConversion.Gray2Bgr);
+ CvInvoke.CvtColor(Image, ImageBgr, ColorConversion.Gray2Bgr);
+
+ ImageSpan = Image.GetBytePointer();
+ ImageBgrSpan = ImageBgr.GetBytePointer();
}
}
- public Mat Image => _image;
+ public Mat Image { get; private set; }
- public Mat ImageBgr { get; set; }
+ public Mat ImageBgr { get; private set; }
+
+ public unsafe byte *ImageSpan { get; private set; }
+ public unsafe byte *ImageBgrSpan { get; private set; }
public VectorOfVectorOfPoint LayerContours
{
@@ -90,7 +96,7 @@ namespace UVtools.WPF
public void Clear()
{
_layer = null;
- _image?.Dispose();
+ Image?.Dispose();
ImageBgr?.Dispose();
_layerContours?.Dispose();
_layerContours = null;
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index 9eb4d6a..aedc4d2 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -433,7 +433,7 @@
-
+
@@ -498,7 +498,9 @@
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
- GridLinesVisibility="All"
+ GridLinesVisibility="Horizontal"
+ SelectionMode="Extended"
+ SelectedIndex="{Binding IssueSelectedIndex, Mode=TwoWay}"
IsReadOnly="True"
ClipboardCopyMode="IncludeHeader"
Items="{Binding Issues}">
@@ -830,6 +832,7 @@