mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-12 11:32:33 +02:00
WPF progress
This commit is contained in:
+2
-1
@@ -31,4 +31,5 @@
|
||||
* Jeremy Lauzon
|
||||
* Peter Teal
|
||||
* Ulrich Mandl
|
||||
* Erwin Ried
|
||||
* Erwin Ried
|
||||
* Matthias Keila
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Multicast event for property change notifications.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler ImageBoxPropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a property already matches a desired value. Sets the property and
|
||||
/// notifies listeners only when necessary.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to a property with both getter and setter.</param>
|
||||
/// <param name="value">Desired value for the property.</param>
|
||||
/// <param name="propertyName">
|
||||
/// Name of the property used to notify listeners. This
|
||||
/// value is optional and can be provided automatically when invoked from compilers that
|
||||
/// support CallerMemberName.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the value was changed, false if the existing value matched the
|
||||
/// desired value.
|
||||
/// </returns>
|
||||
protected bool SetProperty<T>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies listeners that a property value has changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">
|
||||
/// Name of the property used to notify listeners. This
|
||||
/// value is optional and can be provided automatically when invoked from compilers
|
||||
/// that support <see cref="CallerMemberNameAttribute" />.
|
||||
/// </param>
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
var eventHandler = ImageBoxPropertyChanged;
|
||||
eventHandler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Sub Classes
|
||||
|
||||
/// <summary>
|
||||
@@ -377,27 +437,32 @@ namespace UVtools.WPF.Controls
|
||||
public static readonly int MaxZoom = 3500;
|
||||
#endregion
|
||||
|
||||
public static StyledProperty<byte> GridCellSizeProperty =
|
||||
AvaloniaProperty.Register<AdvancedImageBox, byte>(nameof(GridCellSize), 8, false, BindingMode.TwoWay);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the basic cell size
|
||||
/// </summary>
|
||||
public byte GridCellSize
|
||||
{
|
||||
get => GetValue(GridCellSizeProperty);
|
||||
set => SetValue(GridCellSizeProperty, value);
|
||||
get => _gridCellSize;
|
||||
set => SetProperty(ref _gridCellSize, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color used to create the checkerboard style background
|
||||
/// </summary>
|
||||
public ISolidColorBrush GridColor { get; set; } = Brushes.Gainsboro;
|
||||
public ISolidColorBrush GridColor
|
||||
{
|
||||
get => _gridColor;
|
||||
set => SetProperty(ref _gridColor, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color used to create the checkerboard style background
|
||||
/// </summary>
|
||||
public ISolidColorBrush GridColorAlternate { get; set; } = Brushes.White;
|
||||
public ISolidColorBrush GridColorAlternate
|
||||
{
|
||||
get => _gridColorAlternate;
|
||||
set => SetProperty(ref _gridColorAlternate, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <summary>
|
||||
/// Gets or sets if the checkerboard background should be displayed
|
||||
/// </summary>
|
||||
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
|
||||
/// <c>true</c> if the specified point is located within the image view port; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public bool IsPointInImage(double x, double y)
|
||||
=> IsPointInImage(new Point(x, y));
|
||||
=> IsPointInImage(new Point((int) x, (int) y));
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given client size point to represent a coordinate on the source image.
|
||||
@@ -740,9 +817,12 @@ namespace UVtools.WPF.Controls
|
||||
/// </summary>
|
||||
/// <param name="x">The X co-ordinate of the point to convert.</param>
|
||||
/// <param name="y">The Y co-ordinate of the point to convert.</param>
|
||||
/// <param name="fitToBounds">
|
||||
/// if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
|
||||
/// </param>
|
||||
/// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// 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 <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
|
||||
/// </param>
|
||||
/// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
|
||||
public Point PointToImage(double x, double y, bool fitToBounds)
|
||||
=> PointToImage(new Point(x, y), fitToBounds);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the given client size point to represent a coordinate on the source image.
|
||||
/// </summary>
|
||||
/// <param name="x">The X co-ordinate of the point to convert.</param>
|
||||
/// <param name="y">The Y co-ordinate of the point to convert.</param>
|
||||
/// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -777,20 +848,20 @@ namespace UVtools.WPF.Controls
|
||||
/// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
|
||||
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
|
||||
/// <param name="relativeX">The X co-ordinate relative to the <c>x</c> parameter.</param>
|
||||
/// <param name="relativeY">The Y co-ordinate relative to the <c>y</c> parameter.</param>
|
||||
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));
|
||||
|
||||
/// <summary>
|
||||
/// 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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -846,7 +923,7 @@ namespace UVtools.WPF.Controls
|
||||
/// <param name="source">The source of the zoom operation.</param>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the view port to fit the given region
|
||||
/// </summary>
|
||||
/// <param name="x">The X co-ordinate of the selection region.</param>
|
||||
/// <param name="y">The Y co-ordinate of the selection region.</param>
|
||||
/// <param name="width">The width of the selection region.</param>
|
||||
/// <param name="height">The height of the selection region.</param>
|
||||
public void ZoomToRegion(double x, double y, double width, double height)
|
||||
{
|
||||
ZoomToRegion(new Rectangle((int)x, (int)y, (int)width, (int)height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the view port to fit the given region
|
||||
/// </summary>
|
||||
/// <param name="x">The X co-ordinate of the selection region.</param>
|
||||
/// <param name="y">The Y co-ordinate of the selection region.</param>
|
||||
/// <param name="width">The width of the selection region.</param>
|
||||
/// <param name="height">The height of the selection region.</param>
|
||||
public void ZoomToRegion(int x, int y, int width, int height)
|
||||
{
|
||||
ZoomToRegion(new Rectangle(x, y, width, height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the view port to fit the given region
|
||||
/// </summary>
|
||||
/// <param name="rectangle">The rectangle to fit the view port to.</param>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the view port to fit the given region
|
||||
/// </summary>
|
||||
/// <param name="rectangle">The rectangle to fit the view port to.</param>
|
||||
public virtual void ZoomToRegion(Rect rectangle) => ZoomToRegion(rectangle.ToDotNet());
|
||||
|
||||
/// <summary>
|
||||
/// Centers the given point in the image in the center of the control
|
||||
/// </summary>
|
||||
/// <param name="imageLocation">The point of the image to attempt to center.</param>
|
||||
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)));
|
||||
|
||||
/// <summary>
|
||||
/// Centers the given point in the image in the center of the control
|
||||
@@ -950,14 +1073,14 @@ namespace UVtools.WPF.Controls
|
||||
/// <param name="x">The X co-ordinate of the point to center.</param>
|
||||
/// <param name="y">The Y co-ordinate of the point to center.</param>
|
||||
public void CenterAt(double x, double y)
|
||||
=> CenterAt(new Point(x, y));
|
||||
=> CenterAt(new Point((int) x, (int) y));
|
||||
|
||||
/// <summary>
|
||||
/// Resets the viewport to show the center of the image.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source <see cref="T:System.Drawing.Point" /> scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="x">The X co-ordinate of the point to scale.</param>
|
||||
/// <param name="y">The Y co-ordinate of the point to scale.</param>
|
||||
/// <returns>A <see cref="Point"/> which has been scaled to match the current zoom level</returns>
|
||||
public Point GetScaledPoint(int x, int y)
|
||||
{
|
||||
return GetScaledPoint(new Point(x, y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source <see cref="T:System.Drawing.Point" /> scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="x">The X co-ordinate of the point to scale.</param>
|
||||
/// <param name="y">The Y co-ordinate of the point to scale.</param>
|
||||
/// <returns>A <see cref="Point"/> which has been scaled to match the current zoom level</returns>
|
||||
public PointF GetScaledPoint(float x, float y)
|
||||
{
|
||||
return GetScaledPoint(new PointF(x, y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source <see cref="T:System.Drawing.Point" /> scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Point"/> to scale.</param>
|
||||
/// <returns>A <see cref="Point"/> which has been scaled to match the current zoom level</returns>
|
||||
public virtual Point GetScaledPoint(Point source)
|
||||
{
|
||||
return new Point((int)(source.X * this.ZoomFactor), (int)(source.Y * this.ZoomFactor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source <see cref="T:System.Drawing.PointF" /> scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="PointF"/> to scale.</param>
|
||||
/// <returns>A <see cref="PointF"/> which has been scaled to match the current zoom level</returns>
|
||||
public virtual PointF GetScaledPoint(PointF source)
|
||||
{
|
||||
return new PointF((float)(source.X * this.ZoomFactor), (float)(source.Y * this.ZoomFactor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source rectangle scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="x">The X co-ordinate of the source rectangle.</param>
|
||||
/// <param name="y">The Y co-ordinate of the source rectangle.</param>
|
||||
/// <param name="width">The width of the rectangle.</param>
|
||||
/// <param name="height">The height of the rectangle.</param>
|
||||
/// <returns>A <see cref="Rectangle"/> which has been scaled to match the current zoom level</returns>
|
||||
public Rectangle GetScaledRectangle(int x, int y, int width, int height)
|
||||
{
|
||||
return GetScaledRectangle(new Rectangle(x, y, width, height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source rectangle scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="x">The X co-ordinate of the source rectangle.</param>
|
||||
/// <param name="y">The Y co-ordinate of the source rectangle.</param>
|
||||
/// <param name="width">The width of the rectangle.</param>
|
||||
/// <param name="height">The height of the rectangle.</param>
|
||||
/// <returns>A <see cref="RectangleF"/> which has been scaled to match the current zoom level</returns>
|
||||
public RectangleF GetScaledRectangle(float x, float y, float width, float height)
|
||||
{
|
||||
return GetScaledRectangle(new RectangleF(x, y, width, height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source rectangle scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="location">The location of the source rectangle.</param>
|
||||
/// <param name="size">The size of the source rectangle.</param>
|
||||
/// <returns>A <see cref="Rectangle"/> which has been scaled to match the current zoom level</returns>
|
||||
public Rectangle GetScaledRectangle(Point location, Size size)
|
||||
{
|
||||
return GetScaledRectangle(new Rectangle(location, size));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source rectangle scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="location">The location of the source rectangle.</param>
|
||||
/// <param name="size">The size of the source rectangle.</param>
|
||||
/// <returns>A <see cref="Rectangle"/> which has been scaled to match the current zoom level</returns>
|
||||
public RectangleF GetScaledRectangle(PointF location, SizeF size)
|
||||
{
|
||||
return GetScaledRectangle(new RectangleF(location, size));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source <see cref="T:System.Drawing.Rectangle" /> scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Rectangle"/> to scale.</param>
|
||||
/// <returns>A <see cref="Rectangle"/> which has been scaled to match the current zoom level</returns>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source <see cref="T:System.Drawing.RectangleF" /> scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="RectangleF"/> to scale.</param>
|
||||
/// <returns>A <see cref="RectangleF"/> which has been scaled to match the current zoom level</returns>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source size scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="width">The width of the size to scale.</param>
|
||||
/// <param name="height">The height of the size to scale.</param>
|
||||
/// <returns>A <see cref="SizeF"/> which has been resized to match the current zoom level</returns>
|
||||
public SizeF GetScaledSize(float width, float height)
|
||||
{
|
||||
return this.GetScaledSize(new SizeF(width, height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source size scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="width">The width of the size to scale.</param>
|
||||
/// <param name="height">The height of the size to scale.</param>
|
||||
/// <returns>A <see cref="Size"/> which has been resized to match the current zoom level</returns>
|
||||
public Size GetScaledSize(int width, int height)
|
||||
{
|
||||
return this.GetScaledSize(new Size(width, height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source <see cref="T:System.Drawing.SizeF" /> scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="SizeF"/> to scale.</param>
|
||||
/// <returns>A <see cref="SizeF"/> which has been resized to match the current zoom level</returns>
|
||||
public virtual SizeF GetScaledSize(SizeF source)
|
||||
{
|
||||
return new SizeF((float)(source.Width * this.ZoomFactor), (float)(source.Height * this.ZoomFactor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the source <see cref="T:System.Drawing.Size" /> scaled according to the current zoom level
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Size"/> to scale.</param>
|
||||
/// <returns>A <see cref="Size"/> which has been resized to match the current zoom level</returns>
|
||||
public virtual Size GetScaledSize(Size source)
|
||||
{
|
||||
return new Size((int)(source.Width * this.ZoomFactor), (int)(source.Height * this.ZoomFactor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a selection region which encompasses the entire image
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">Thrown if no image is currently set</exception>
|
||||
public virtual void SelectAll()
|
||||
{
|
||||
if (Image is null) return;
|
||||
SelectionRegion = new RectangleF(0, 0, (float) Image.Size.Width, (float) Image.Size.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears any existing selection region
|
||||
/// </summary>
|
||||
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.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image view port.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -433,7 +433,7 @@
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="\{0\}/\{1\}">
|
||||
<Binding Path="#IssuesGrid.SelectedIndex"/>
|
||||
<Binding Path="IssueSelectedIndexStr"/>
|
||||
<Binding Path="Issues.Count"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
@@ -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 @@
|
||||
|
||||
<Button
|
||||
ToolTip.Tip="Object volume bounds for current layer, position and size. Click: go to region"
|
||||
Command="{Binding ZoomToFitPrintVolume}"
|
||||
>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
|
||||
<Image Source="/Assets/Icons/expand-16x16.png"/>
|
||||
@@ -871,7 +874,7 @@
|
||||
|
||||
<Button
|
||||
ToolTip.Tip="Layer Resolution. Click: Zoom to fit"
|
||||
Command="{Binding ZoomToFit}"
|
||||
Command="{Binding ZoomToFitSimple}"
|
||||
>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
|
||||
<Image Source="/Assets/Icons/expand-16x16.png"/>
|
||||
|
||||
+441
-72
@@ -6,9 +6,11 @@
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -17,14 +19,16 @@ using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using DynamicData;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using Emgu.CV.Util;
|
||||
using MessageBox.Avalonia.Enums;
|
||||
using ReactiveUI;
|
||||
using UVtools.Core;
|
||||
using UVtools.Core.Extensions;
|
||||
using UVtools.Core.FileFormats;
|
||||
@@ -33,7 +37,10 @@ using UVtools.WPF.Controls;
|
||||
using UVtools.WPF.Extensions;
|
||||
using UVtools.WPF.Structures;
|
||||
using UVtools.WPF.Windows;
|
||||
using Bitmap = Avalonia.Media.Imaging.Bitmap;
|
||||
using Color = UVtools.WPF.Structures.Color;
|
||||
using Helpers = UVtools.WPF.Controls.Helpers;
|
||||
using Point = Avalonia.Point;
|
||||
|
||||
namespace UVtools.WPF
|
||||
{
|
||||
@@ -54,6 +61,8 @@ namespace UVtools.WPF
|
||||
public SliderEx LayerSlider;
|
||||
public Panel LayerNavigationTooltipPanel;
|
||||
public Border LayerNavigationTooltipBorder;
|
||||
|
||||
public DataGrid IssuesGrid;
|
||||
|
||||
#region DataSets
|
||||
public ObservableCollection<SlicerProperty> SlicerProperties { get; } = new ObservableCollection<SlicerProperty>();
|
||||
@@ -73,6 +82,9 @@ namespace UVtools.WPF
|
||||
private int _tabSelectedIndex;
|
||||
private uint _visibleThumbnailIndex;
|
||||
private Bitmap _visibleThumbnailImage;
|
||||
|
||||
private int _issueSelectedIndex = -1;
|
||||
|
||||
private bool _isVerbose;
|
||||
private bool _showLayerImageRotated;
|
||||
private bool _showLayerImageDifference;
|
||||
@@ -374,6 +386,82 @@ namespace UVtools.WPF
|
||||
|
||||
#region Issues
|
||||
|
||||
public int IssueSelectedIndex
|
||||
{
|
||||
get => _issueSelectedIndex;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _issueSelectedIndex, value);
|
||||
OnPropertyChanged(nameof(IssueSelectedIndexStr));
|
||||
}
|
||||
}
|
||||
|
||||
public string IssueSelectedIndexStr => (_issueSelectedIndex+1).ToString().PadLeft(Issues.Count.ToString().Length, '0');
|
||||
|
||||
private void IssuesGridOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
//Debug.WriteLine(IssuesGrid.SelectedIndex);
|
||||
//Debug.WriteLine(IssuesGrid.SelectedItems.Count);
|
||||
if (IssuesGrid.SelectedItems.Count > 1) return;
|
||||
|
||||
}
|
||||
|
||||
private void IssuesGridOnCellPointerPressed(object? sender, DataGridCellPointerPressedEventArgs e)
|
||||
{
|
||||
if(!(IssuesGrid.SelectedItem is LayerIssue issue)) return;
|
||||
// Double clicking an issue will center and zoom into the
|
||||
// selected issue. Left click on an issue will zoom to fit.
|
||||
|
||||
var pointer = e.PointerPressedEventArgs.GetCurrentPoint(IssuesGrid);
|
||||
|
||||
if (e.PointerPressedEventArgs.ClickCount == 1)
|
||||
{
|
||||
if (issue.Type == LayerIssue.IssueType.TouchingBound || issue.Type == LayerIssue.IssueType.EmptyLayer ||
|
||||
(issue.X == -1 && issue.Y == -1))
|
||||
{
|
||||
ZoomToFit();
|
||||
}
|
||||
else if (issue.X >= 0 && issue.Y >= 0)
|
||||
{
|
||||
if (Settings.LayerPreview.ZoomIssues /*^ (ModifierKeys & Keys.Alt) != 0*/)
|
||||
{
|
||||
ZoomToIssue(issue);
|
||||
}
|
||||
else
|
||||
{
|
||||
//CenterLayerAt(GetTransposedIssueBounds(issue));
|
||||
// If issue is not already visible, center on it and bring it into view.
|
||||
// Issues already in view will not be centered, though their color may
|
||||
// change and the crosshair may move to reflect active selections.
|
||||
|
||||
if (!LayerImageBox.GetSourceImageRegion().Contains(GetTransposedIssueBounds(issue)))
|
||||
{
|
||||
CenterAtIssue(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ForceUpdateActualLayer(issue.LayerIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.PointerPressedEventArgs.ClickCount == 2)
|
||||
{
|
||||
if (pointer.Properties.IsLeftButtonPressed)
|
||||
{
|
||||
ZoomToIssue(issue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pointer.Properties.IsRightButtonPressed)
|
||||
{
|
||||
ZoomToFit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void OnClickDetectIssues()
|
||||
{
|
||||
if (!IsFileLoaded) return;
|
||||
@@ -642,6 +730,7 @@ namespace UVtools.WPF
|
||||
_actualLayer = layerIndex;
|
||||
ShowLayer();
|
||||
InvalidateLayerNavigation();
|
||||
OnPropertyChanged(nameof(ActualLayer));
|
||||
}
|
||||
|
||||
public void InvalidateLayerNavigation()
|
||||
@@ -694,6 +783,9 @@ namespace UVtools.WPF
|
||||
LayerSlider = this.FindControl<SliderEx>("Layer.Navigation.Slider");
|
||||
LayerNavigationTooltipPanel = this.FindControl<Panel>("Layer.Navigation.Tooltip.Panel");
|
||||
LayerNavigationTooltipBorder = this.FindControl<Border>("Layer.Navigation.Tooltip.Border");
|
||||
IssuesGrid = this.FindControl<DataGrid>("IssuesGrid");
|
||||
IssuesGrid.SelectionChanged += IssuesGridOnSelectionChanged;
|
||||
IssuesGrid.CellPointerPressed += IssuesGridOnCellPointerPressed;
|
||||
|
||||
_showLayerImageDifference = Settings.LayerPreview.ShowLayerDifference;
|
||||
_showLayerOutlinePrintVolumeBoundary = Settings.LayerPreview.VolumeBoundsOutline;
|
||||
@@ -771,6 +863,8 @@ namespace UVtools.WPF
|
||||
VisibleThumbnailIndex = 0;
|
||||
LayerImageBox.Image = null;
|
||||
SlicerProperties.Clear();
|
||||
Issues.Clear();
|
||||
Drawings.Clear();
|
||||
ResetDataContext();
|
||||
}
|
||||
|
||||
@@ -905,7 +999,7 @@ namespace UVtools.WPF
|
||||
{
|
||||
ZoomToFit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GoFirstLayer()
|
||||
{
|
||||
@@ -935,11 +1029,6 @@ namespace UVtools.WPF
|
||||
ActualLayer = SliderMaximumValue;
|
||||
}
|
||||
|
||||
public void ZoomToFit()
|
||||
{
|
||||
LayerImageBox.ZoomToFit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a layer number
|
||||
/// </summary>
|
||||
@@ -955,8 +1044,8 @@ namespace UVtools.WPF
|
||||
//var imageSpan = LayerCache.Image.GetPixelSpan<byte>();
|
||||
//var imageBgrSpan = LayerCache.ImageBgr.GetPixelSpan<byte>();
|
||||
|
||||
var imageSpan = LayerCache.Image.GetBytePointer();
|
||||
var imageBgrSpan = LayerCache.ImageBgr.GetBytePointer();
|
||||
var imageSpan = LayerCache.ImageSpan;
|
||||
var imageBgrSpan = LayerCache.ImageBgrSpan;
|
||||
|
||||
if (_showLayerOutlineEdgeDetection)
|
||||
{
|
||||
@@ -1023,77 +1112,76 @@ namespace UVtools.WPF
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
var selectedIssues = flvIssues.SelectedObjects;
|
||||
|
||||
if (btnLayerImageHighlightIssues.Checked &&
|
||||
!ReferenceEquals(Issues, null))
|
||||
|
||||
var selectedIssues = IssuesGrid.SelectedItems;
|
||||
|
||||
if (_showLayerImageIssues && Issues.Count > 0)
|
||||
{
|
||||
foreach (var issue in Issues)
|
||||
{
|
||||
foreach (var issue in Issues)
|
||||
if (issue.LayerIndex != ActualLayer) continue;
|
||||
if (!issue.HaveValidPoint) continue;
|
||||
|
||||
Color color = Color.Empty;
|
||||
|
||||
if (issue.Type == LayerIssue.IssueType.ResinTrap)
|
||||
{
|
||||
if (issue.LayerIndex != ActualLayer) continue;
|
||||
if (!issue.HaveValidPoint) continue;
|
||||
|
||||
Color color = Color.Empty;
|
||||
|
||||
if (issue.Type == LayerIssue.IssueType.ResinTrap)
|
||||
color = selectedIssues.Contains(issue)
|
||||
? Settings.LayerPreview.ResinTrapHighlightColor
|
||||
: Settings.LayerPreview.ResinTrapColor;
|
||||
|
||||
|
||||
using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels)))
|
||||
{
|
||||
CvInvoke.DrawContours(LayerCache.ImageBgr, vec, -1,
|
||||
new MCvScalar(color.B, color.G, color.R), -1);
|
||||
}
|
||||
|
||||
if (_showLayerImageCrosshairs &&
|
||||
!Settings.LayerPreview.CrosshairShowOnlyOnSelectedIssues &&
|
||||
LayerImageBox.Zoom <= AppSettings.CrosshairFadeLevel)
|
||||
{
|
||||
DrawCrosshair(issue.BoundingRectangle);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (issue.Type)
|
||||
{
|
||||
case LayerIssue.IssueType.Island:
|
||||
color = selectedIssues.Contains(issue)
|
||||
? Settings.Default.ResinTrapHLColor
|
||||
: Settings.Default.ResinTrapColor;
|
||||
|
||||
|
||||
using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels)))
|
||||
{
|
||||
CvInvoke.DrawContours(ActualLayerImageBgr, vec, -1,
|
||||
new MCvScalar(color.B, color.G, color.R), -1);
|
||||
}
|
||||
|
||||
if (btnLayerImageShowCrosshairs.Checked &&
|
||||
!Settings.Default.CrosshairShowOnlyOnSelectedIssues &&
|
||||
pbLayer.Zoom <= CrosshairFadeLevel)
|
||||
? Settings.LayerPreview.IslandHighlightColor
|
||||
: Settings.LayerPreview.IslandColor;
|
||||
if (_showLayerImageCrosshairs &&
|
||||
!Settings.LayerPreview.CrosshairShowOnlyOnSelectedIssues &&
|
||||
LayerImageBox.Zoom <= AppSettings.CrosshairFadeLevel)
|
||||
{
|
||||
DrawCrosshair(issue.BoundingRectangle);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (issue.Type)
|
||||
{
|
||||
case LayerIssue.IssueType.Island:
|
||||
color = selectedIssues.Contains(issue)
|
||||
? Settings.Default.IslandHLColor
|
||||
: Settings.Default.IslandColor;
|
||||
if (btnLayerImageShowCrosshairs.Checked &&
|
||||
!Settings.Default.CrosshairShowOnlyOnSelectedIssues &&
|
||||
pbLayer.Zoom <= CrosshairFadeLevel)
|
||||
{
|
||||
DrawCrosshair(issue.BoundingRectangle);
|
||||
}
|
||||
|
||||
break;
|
||||
case LayerIssue.IssueType.TouchingBound:
|
||||
color = Settings.Default.TouchingBoundsColor;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var pixel in issue)
|
||||
{
|
||||
int pixelPos = ActualLayerImage.GetPixelPos(pixel);
|
||||
byte brightness = imageSpan[pixelPos];
|
||||
if (brightness == 0) continue;
|
||||
|
||||
int pixelBgrPos = pixelPos * ActualLayerImageBgr.NumberOfChannels;
|
||||
|
||||
var newColor = color.FactorColor(brightness, 80);
|
||||
|
||||
imageBgrSpan[pixelBgrPos] = newColor.B; // B
|
||||
imageBgrSpan[pixelBgrPos + 1] = newColor.G; // G
|
||||
imageBgrSpan[pixelBgrPos + 2] = newColor.R; // R
|
||||
}
|
||||
|
||||
break;
|
||||
case LayerIssue.IssueType.TouchingBound:
|
||||
color = Settings.LayerPreview.TouchingBoundsColor;
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
foreach (var pixel in issue)
|
||||
{
|
||||
int pixelPos = LayerCache.Image.GetPixelPos(pixel);
|
||||
byte brightness = imageSpan[pixelPos];
|
||||
if (brightness == 0) continue;
|
||||
|
||||
int pixelBgrPos = pixelPos * LayerCache.ImageBgr.NumberOfChannels;
|
||||
|
||||
var newColor = color.FactorColor(brightness, 80);
|
||||
|
||||
imageBgrSpan[pixelBgrPos] = newColor.B; // B
|
||||
imageBgrSpan[pixelBgrPos + 1] = newColor.G; // G
|
||||
imageBgrSpan[pixelBgrPos + 2] = newColor.R; // R
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_showLayerOutlinePrintVolumeBoundary)
|
||||
{
|
||||
@@ -1270,6 +1358,287 @@ namespace UVtools.WPF
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a crosshair around a rectangle
|
||||
/// </summary>
|
||||
/// <param name="rect"></param>
|
||||
public void DrawCrosshair(Rectangle rect)
|
||||
{
|
||||
// Gradually increase line thickness from 1 to 3 at the lower-end of the zoom range.
|
||||
// This prevents the crosshair lines from disappearing due to being too thin to
|
||||
// render at very low zoom factors.
|
||||
var lineThickness = (LayerImageBox.Zoom > 100) ? 1 : (LayerImageBox.Zoom < 50) ? 3 : 2;
|
||||
var color = new MCvScalar(Settings.LayerPreview.CrosshairColor.B, Settings.LayerPreview.CrosshairColor.G,
|
||||
Settings.LayerPreview.CrosshairColor.R);
|
||||
|
||||
|
||||
// LEFT
|
||||
var startPoint = new System.Drawing.Point(Math.Max(0, rect.X - Settings.LayerPreview.CrosshairMargin - 1),
|
||||
rect.Y + rect.Height / 2);
|
||||
var endPoint =
|
||||
new System.Drawing.Point(
|
||||
Settings.LayerPreview.CrosshairLength == 0
|
||||
? 0
|
||||
: (int)Math.Max(0, startPoint.X - Settings.LayerPreview.CrosshairLength + 1),
|
||||
startPoint.Y);
|
||||
|
||||
CvInvoke.Line(LayerCache.ImageBgr,
|
||||
startPoint,
|
||||
endPoint,
|
||||
color,
|
||||
lineThickness);
|
||||
|
||||
|
||||
// RIGHT
|
||||
startPoint.X = Math.Min(LayerCache.ImageBgr.Width,
|
||||
rect.Right + Settings.LayerPreview.CrosshairMargin);
|
||||
endPoint.X = Settings.LayerPreview.CrosshairLength == 0
|
||||
? LayerCache.ImageBgr.Width
|
||||
: (int)Math.Min(LayerCache.ImageBgr.Width, startPoint.X + Settings.LayerPreview.CrosshairLength - 1);
|
||||
|
||||
CvInvoke.Line(LayerCache.ImageBgr,
|
||||
startPoint,
|
||||
endPoint,
|
||||
color,
|
||||
lineThickness);
|
||||
|
||||
// TOP
|
||||
startPoint = new System.Drawing.Point(rect.X + rect.Width / 2,
|
||||
Math.Max(0, rect.Y - Settings.LayerPreview.CrosshairMargin - 1));
|
||||
endPoint = new System.Drawing.Point(startPoint.X,
|
||||
(int)(Settings.LayerPreview.CrosshairLength == 0
|
||||
? 0
|
||||
: Math.Max(0, startPoint.Y - Settings.LayerPreview.CrosshairLength + 1)));
|
||||
|
||||
|
||||
CvInvoke.Line(LayerCache.ImageBgr,
|
||||
startPoint,
|
||||
endPoint,
|
||||
color,
|
||||
lineThickness);
|
||||
|
||||
// Bottom
|
||||
startPoint.Y = Math.Min(LayerCache.ImageBgr.Height, rect.Bottom + Settings.LayerPreview.CrosshairMargin);
|
||||
endPoint.Y = Settings.LayerPreview.CrosshairLength == 0
|
||||
? LayerCache.ImageBgr.Height
|
||||
: (int)Math.Min(LayerCache.ImageBgr.Height, startPoint.Y + Settings.LayerPreview.CrosshairLength - 1);
|
||||
|
||||
CvInvoke.Line(LayerCache.ImageBgr,
|
||||
startPoint,
|
||||
endPoint,
|
||||
color,
|
||||
lineThickness);
|
||||
}
|
||||
|
||||
#region Zoom
|
||||
public Point GetTransposedPoint(Point point, bool clockWise = true)
|
||||
{
|
||||
if (!_showLayerImageRotated) return point;
|
||||
return clockWise
|
||||
? new Point(point.Y, LayerCache.Image.Height - 1 - point.X)
|
||||
: new Point(LayerCache.Image.Height - 1 - point.Y, point.X);
|
||||
}
|
||||
|
||||
public Rectangle GetTransposedRectangle(RectangleF rectangleF, bool clockWise = true, bool ignoreLayerRotation = false) =>
|
||||
GetTransposedRectangle(Rectangle.Round(rectangleF), clockWise, ignoreLayerRotation);
|
||||
|
||||
public Rectangle GetTransposedRectangle(Rectangle rectangle, bool clockWise = true, bool ignoreLayerRotation = false)
|
||||
{
|
||||
if (rectangle.IsEmpty || (!ignoreLayerRotation && !_showLayerImageRotated)) return rectangle;
|
||||
return clockWise
|
||||
? new Rectangle(LayerCache.Image.Height - rectangle.Bottom,
|
||||
rectangle.Left, rectangle.Height, rectangle.Width)
|
||||
//: new Rectangle(ActualLayerImage.Width - rectangle.Bottom, rectangle.Left, rectangle.Width, rectangle.Height);
|
||||
//: new Rectangle(ActualLayerImage.Width - rectangle.Bottom, ActualLayerImage.Height-rectangle.Right, rectangle.Width, rectangle.Height); // Rotate90FlipX: // = Rotate270FlipY
|
||||
//: new Rectangle(rectangle.Top, rectangle.Left, rectangle.Width, rectangle.Height); // Rotate270FlipX: // = Rotate90FlipY
|
||||
: new Rectangle(rectangle.Top, LayerCache.Image.Height - rectangle.Right, rectangle.Height, rectangle.Width); // Rotate90FlipNone: // = Rotate270FlipXY
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding rectangle of the passed issue, automatically adjusting
|
||||
/// the coordinates and width/height to account for whether or not the layer
|
||||
/// preview image is rotated. Used to ensure images are properly zoomed or
|
||||
/// centered independent of the layer preview rotation.
|
||||
/// </summary>
|
||||
private Rectangle GetTransposedIssueBounds(LayerIssue issue)
|
||||
{
|
||||
if (issue.X >= 0 && issue.Y >= 0 && (issue.BoundingRectangle.IsEmpty || issue.Size == 1) &&
|
||||
_showLayerImageRotated)
|
||||
return new Rectangle(LayerCache.Image.Height - 1 - issue.Y,
|
||||
issue.X, 1, 1);
|
||||
|
||||
return GetTransposedRectangle(issue.BoundingRectangle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Centers layer view on a X,Y coordinate
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate</param>
|
||||
/// <param name="y">X coordinate</param>
|
||||
/// <param name="zoomLevel">Zoom level to set, 0 to ignore or negative value to get current locked zoom level</param>
|
||||
public void CenterLayerAt(double x, double y, int zoomLevel = 0)
|
||||
{
|
||||
if (zoomLevel < 0) zoomLevel = AppSettings.LockedZoomLevel;
|
||||
if (zoomLevel > 0) LayerImageBox.Zoom = zoomLevel;
|
||||
LayerImageBox.CenterAt(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Centers layer view on a X,Y coordinate
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate</param>
|
||||
/// <param name="y">X coordinate</param>
|
||||
/// <param name="zoomLevel">Zoom level to set, 0 to ignore or negative value to get current locked zoom level</param>
|
||||
public void CenterLayerAt(int x, int y, int zoomLevel = 0)
|
||||
{
|
||||
if (zoomLevel < 0) zoomLevel = AppSettings.LockedZoomLevel;
|
||||
if (zoomLevel > 0) LayerImageBox.Zoom = zoomLevel;
|
||||
LayerImageBox.CenterAt(x, y);
|
||||
}
|
||||
|
||||
|
||||
public void CenterLayerAt(Rectangle rectangle, int zoomLevel = 0, bool zoomToRegion = false) => CenterLayerAt(rectangle.ToAvalonia(), zoomLevel, zoomToRegion);
|
||||
|
||||
/// <summary>
|
||||
/// Centers layer view on a middle of a given rectangle
|
||||
/// </summary>
|
||||
/// <param name="rectangle">Rectangle holding coordinates and bounds</param>
|
||||
/// <param name="zoomLevel">Zoom level to set, 0 to ignore or negative value to get current locked zoom level</param>
|
||||
/// <param name="zoomToRegion">Auto zoom to a region and ensure that region area stays all visible when possible, when true this will overwrite zoomLevel</param></param>
|
||||
public void CenterLayerAt(Rect rectangle, int zoomLevel = 0, bool zoomToRegion = false)
|
||||
{
|
||||
var viewPort = LayerImageBox.GetSourceImageRegion();
|
||||
if (zoomToRegion ||
|
||||
rectangle.Width * AppSettings.LockedZoomLevel / LayerImageBox.Zoom > viewPort.Width ||
|
||||
rectangle.Height * AppSettings.LockedZoomLevel / LayerImageBox.Zoom > viewPort.Height)
|
||||
{
|
||||
//SupressLayerZoomEvent = true;
|
||||
LayerImageBox.ZoomToRegion(rectangle);
|
||||
//SupressLayerZoomEvent = false;
|
||||
//pbLayer.ZoomOut(true);
|
||||
return;
|
||||
}
|
||||
|
||||
CenterLayerAt(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2, zoomLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Centers layer view on a <see cref="Point"/>
|
||||
/// </summary>
|
||||
/// <param name="point">Point holding X and Y coordinates</param>
|
||||
/// <param name="zoomLevel">Zoom level to set, 0 to ignore or negative value to get current locked zoom level</param>
|
||||
public void CenterLayerAt(System.Drawing.Point point, int zoomLevel = 0) => CenterLayerAt(point.X, point.Y, zoomLevel);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Zoom the layer preview to the passed issue, or if appropriate for issue type,
|
||||
/// Zoom to fit the plate or print bounds.
|
||||
/// </summary>
|
||||
private void ZoomToIssue(LayerIssue issue)
|
||||
{
|
||||
if (issue.Type == LayerIssue.IssueType.TouchingBound || issue.Type == LayerIssue.IssueType.EmptyLayer ||
|
||||
(issue.X == -1 && issue.Y == -1))
|
||||
{
|
||||
ZoomToFit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (issue.X >= 0 && issue.Y >= 0)
|
||||
{
|
||||
// Check to see if this zoom action will cross the crosshair fade threshold
|
||||
/*if (tsLayerImageShowCrosshairs.Checked && !ReferenceEquals(Issues, null) && flvIssues.SelectedIndices.Count > 0
|
||||
&& pbLayer.Zoom <= CrosshairFadeLevel && LockedZoomLevel > CrosshairFadeLevel)
|
||||
{
|
||||
// Refresh the preview without the crosshairs before zooming-in.
|
||||
// Prevents zoomed-in crosshairs from breifly being displayed before
|
||||
// the Layer Preview is refreshed post-zoom.
|
||||
tsLayerImageShowCrosshairs.Checked = false;
|
||||
ShowLayer();
|
||||
tsLayerImageShowCrosshairs.Checked = true;
|
||||
}*/
|
||||
|
||||
CenterLayerAt(GetTransposedIssueBounds(issue), AppSettings.LockedZoomLevel);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Center the layer preview on the passed issue, or if appropriate for issue type,
|
||||
/// Zoom to fit the plate or print bounds.
|
||||
/// </summary>
|
||||
private void CenterAtIssue(LayerIssue issue)
|
||||
{
|
||||
if (issue.Type == LayerIssue.IssueType.TouchingBound || issue.Type == LayerIssue.IssueType.EmptyLayer ||
|
||||
(issue.X == -1 && issue.Y == -1))
|
||||
{
|
||||
ZoomToFit();
|
||||
}
|
||||
|
||||
if (issue.X >= 0 && issue.Y >= 0)
|
||||
{
|
||||
CenterLayerAt(GetTransposedIssueBounds(issue));
|
||||
}
|
||||
}
|
||||
|
||||
public void ZoomToFitSimple()
|
||||
{
|
||||
LayerImageBox.ZoomToFit();
|
||||
}
|
||||
|
||||
public void ZoomToFitPrintVolume()
|
||||
{
|
||||
LayerImageBox.ZoomToRegion(SlicerFile.LayerManager.BoundingRectangle);
|
||||
}
|
||||
|
||||
private void ZoomToFit()
|
||||
{
|
||||
if (ReferenceEquals(SlicerFile, null)) return;
|
||||
|
||||
// If ALT key is pressed when ZoomToFit is performed, the configured option for
|
||||
// zoom to plate vs. zoom to print bounds will be inverted.
|
||||
|
||||
if (Settings.LayerPreview.ZoomToFitPrintVolumeBounds /*^ (Application. & KeyModifiers.Alt) != 0*/)
|
||||
{
|
||||
if (!_showLayerImageRotated)
|
||||
{
|
||||
LayerImageBox.ZoomToRegion(SlicerFile.LayerManager.BoundingRectangle);
|
||||
}
|
||||
else
|
||||
{
|
||||
LayerImageBox.ZoomToRegion(LayerCache.Image.Height - 1 - SlicerFile.LayerManager.BoundingRectangle.Bottom,
|
||||
SlicerFile.LayerManager.BoundingRectangle.X,
|
||||
SlicerFile.LayerManager.BoundingRectangle.Height,
|
||||
SlicerFile.LayerManager.BoundingRectangle.Width
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LayerImageBox.ZoomToFit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If there is an issue under the point location passed, that issue will be selected and
|
||||
/// scrolled into view on the IssueList.
|
||||
/// </summary>
|
||||
private void SelectIssueAtPoint(System.Drawing.Point location)
|
||||
{
|
||||
//location = GetTransposedPoint(location);
|
||||
// If location clicked is within an issue, activate it.
|
||||
for (var i = 0; i < Issues.Count; i++)
|
||||
{
|
||||
LayerIssue issue = Issues[i];
|
||||
|
||||
if (issue.LayerIndex != ActualLayer) continue;
|
||||
if (!GetTransposedIssueBounds(issue).Contains(location)) continue;
|
||||
|
||||
IssueSelectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,5 +63,21 @@ namespace UVtools.WPF.Structures
|
||||
}
|
||||
|
||||
public static Color Empty => new Color(0,0,0,0);
|
||||
|
||||
public Color FactorColor(byte pixelColor, byte min = 0, byte max = byte.MaxValue) =>
|
||||
FactorColor(pixelColor / 255f, min, max);
|
||||
|
||||
public Color FactorColor(float factor, byte min = 0, byte max = byte.MaxValue)
|
||||
{
|
||||
byte r = (byte)(R == 0 ? 0 :
|
||||
Math.Min(Math.Max(min, R * factor), max));
|
||||
|
||||
byte g = (byte)(G == 0 ? 0 :
|
||||
Math.Min(Math.Max(min, G * factor), max));
|
||||
|
||||
byte b = (byte)(B == 0 ? 0 :
|
||||
Math.Min(Math.Max(min, B * factor), max));
|
||||
return Color.FromArgb(A, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user