|
@@ -17,6 +17,7 @@ using InABox.Core;
|
|
|
using SkiaSharp;
|
|
|
using System.Collections.ObjectModel;
|
|
|
using System.Threading.Tasks;
|
|
|
+using Avalonia.LogicalTree;
|
|
|
|
|
|
namespace InABox.Avalonia.Components;
|
|
|
|
|
@@ -181,7 +182,16 @@ public partial class ImageEditor : UserControl
|
|
|
|
|
|
private ObservableCollection<IImageEditorObject> Objects = new();
|
|
|
|
|
|
- private IImageEditorObject? CurrentObject;
|
|
|
+ private IImageEditorObject? _currentObject;
|
|
|
+ private IImageEditorObject? CurrentObject
|
|
|
+ {
|
|
|
+ get => _currentObject;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _currentObject?.SetActive(false);
|
|
|
+ _currentObject = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
private Stack<IImageEditorObject> RedoStack = new();
|
|
|
|
|
@@ -297,6 +307,7 @@ public partial class ImageEditor : UserControl
|
|
|
Mode = mode;
|
|
|
ShapeButton.Content = CreateModeButtonContent(mode);
|
|
|
SecondaryColour.IsVisible = HasSecondaryColour();
|
|
|
+ LineThicknessButton.IsVisible = HasLineThickness();
|
|
|
}
|
|
|
|
|
|
private bool HasSecondaryColour()
|
|
@@ -304,6 +315,14 @@ public partial class ImageEditor : UserControl
|
|
|
return Mode == ImageEditingMode.Rectangle || Mode == ImageEditingMode.Ellipse;
|
|
|
}
|
|
|
|
|
|
+ private bool HasLineThickness()
|
|
|
+ {
|
|
|
+ return Mode == ImageEditingMode.Rectangle
|
|
|
+ || Mode == ImageEditingMode.Ellipse
|
|
|
+ || Mode == ImageEditingMode.Polyline
|
|
|
+ || Mode == ImageEditingMode.Dimension;
|
|
|
+ }
|
|
|
+
|
|
|
#endregion
|
|
|
|
|
|
#region Mode Buttons
|
|
@@ -314,6 +333,7 @@ public partial class ImageEditor : UserControl
|
|
|
AddModeButton(ImageEditingMode.Rectangle);
|
|
|
AddModeButton(ImageEditingMode.Ellipse);
|
|
|
AddModeButton(ImageEditingMode.Text);
|
|
|
+ AddModeButton(ImageEditingMode.Dimension);
|
|
|
}
|
|
|
|
|
|
private void AddModeButton(ImageEditingMode mode)
|
|
@@ -327,27 +347,29 @@ public partial class ImageEditor : UserControl
|
|
|
{
|
|
|
case ImageEditingMode.Polyline:
|
|
|
var canvas = new Canvas();
|
|
|
- var points = new Point[] { new(0, 0), new(20, 8), new(5, 16), new(25, 25) };
|
|
|
- var line1 = new Polyline { Points = points, Width = 25, Height = 25 };
|
|
|
- var line2 = new Polyline { Points = points, Width = 25, Height = 25 };
|
|
|
- line1.StrokeThickness = 4;
|
|
|
- line1.StrokeLineCap = PenLineCap.Round;
|
|
|
- line1.StrokeJoin = PenLineJoin.Round;
|
|
|
- line1.Stroke = new SolidColorBrush(Colors.Black);
|
|
|
- canvas.Children.Add(line1);
|
|
|
-
|
|
|
- if (bindColour)
|
|
|
{
|
|
|
- line1.StrokeThickness = 5;
|
|
|
- line2.StrokeThickness = 4;
|
|
|
- line2.StrokeLineCap = PenLineCap.Round;
|
|
|
- line2.StrokeJoin = PenLineJoin.Round;
|
|
|
- line2.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush))
|
|
|
+ var points = new Point[] { new(0, 0), new(20, 8), new(5, 16), new(25, 25) };
|
|
|
+ var line1 = new Polyline { Points = points, Width = 25, Height = 25 };
|
|
|
+ var line2 = new Polyline { Points = points, Width = 25, Height = 25 };
|
|
|
+ line1.StrokeThickness = 4;
|
|
|
+ line1.StrokeLineCap = PenLineCap.Round;
|
|
|
+ line1.StrokeJoin = PenLineJoin.Round;
|
|
|
+ line1.Stroke = new SolidColorBrush(Colors.Black);
|
|
|
+ canvas.Children.Add(line1);
|
|
|
+
|
|
|
+ if (bindColour)
|
|
|
{
|
|
|
- Source = this,
|
|
|
- Converter = ImageEditorRemoveOpacityConverter.Instance
|
|
|
- });
|
|
|
- canvas.Children.Add(line2);
|
|
|
+ line1.StrokeThickness = 5;
|
|
|
+ line2.StrokeThickness = 4;
|
|
|
+ line2.StrokeLineCap = PenLineCap.Round;
|
|
|
+ line2.StrokeJoin = PenLineJoin.Round;
|
|
|
+ line2.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush))
|
|
|
+ {
|
|
|
+ Source = this,
|
|
|
+ Converter = ImageEditorRemoveOpacityConverter.Instance
|
|
|
+ });
|
|
|
+ canvas.Children.Add(line2);
|
|
|
+ }
|
|
|
}
|
|
|
return canvas;
|
|
|
case ImageEditingMode.Rectangle:
|
|
@@ -417,7 +439,122 @@ public partial class ImageEditor : UserControl
|
|
|
textBox.TextAlignment = TextAlignment.Center;
|
|
|
textBox.HorizontalAlignment = HorizontalAlignment.Center;
|
|
|
textBox.VerticalAlignment = VerticalAlignment.Center;
|
|
|
+ if (bindColour)
|
|
|
+ {
|
|
|
+ textBox.Bind(TextBlock.ForegroundProperty, new Binding(nameof(PrimaryBrush))
|
|
|
+ {
|
|
|
+ Source = this,
|
|
|
+ Converter = ImageEditorRemoveOpacityConverter.Instance
|
|
|
+ });
|
|
|
+ }
|
|
|
return textBox;
|
|
|
+ case ImageEditingMode.Dimension:
|
|
|
+ canvas = new Canvas();
|
|
|
+ canvas.Width = 25;
|
|
|
+ canvas.Height = 25;
|
|
|
+
|
|
|
+ {
|
|
|
+ var dimLines = new List<Line>();
|
|
|
+
|
|
|
+ dimLines.Add(new Line
|
|
|
+ {
|
|
|
+ StartPoint = new(2, 10),
|
|
|
+ EndPoint = new(23, 10),
|
|
|
+ StrokeLineCap = PenLineCap.Round
|
|
|
+ });
|
|
|
+ dimLines.Add(new Line
|
|
|
+ {
|
|
|
+ StartPoint = new(2, 10),
|
|
|
+ EndPoint = new(5, 7),
|
|
|
+ StrokeLineCap = PenLineCap.Square
|
|
|
+ });
|
|
|
+ dimLines.Add(new Line
|
|
|
+ {
|
|
|
+ StartPoint = new(2, 10),
|
|
|
+ EndPoint = new(5, 13),
|
|
|
+ StrokeLineCap = PenLineCap.Square
|
|
|
+ });
|
|
|
+ dimLines.Add(new Line
|
|
|
+ {
|
|
|
+ StartPoint = new(23, 10),
|
|
|
+ EndPoint = new(20, 7),
|
|
|
+ StrokeLineCap = PenLineCap.Square
|
|
|
+ });
|
|
|
+ dimLines.Add(new Line
|
|
|
+ {
|
|
|
+ StartPoint = new(23, 10),
|
|
|
+ EndPoint = new(20, 13),
|
|
|
+ StrokeLineCap = PenLineCap.Square
|
|
|
+ });
|
|
|
+
|
|
|
+ var dotLines = new List<Line>();
|
|
|
+ dotLines.Add(new Line
|
|
|
+ {
|
|
|
+ StartPoint = new(2, 10),
|
|
|
+ EndPoint = new(2, 24),
|
|
|
+ StrokeDashArray = [2, 2]
|
|
|
+ });
|
|
|
+ dotLines.Add(new Line
|
|
|
+ {
|
|
|
+ StartPoint = new(23, 10),
|
|
|
+ EndPoint = new(23, 24),
|
|
|
+ StrokeDashArray = [2, 2]
|
|
|
+ });
|
|
|
+
|
|
|
+ var number = new TextBlock
|
|
|
+ {
|
|
|
+ Text = "10",
|
|
|
+ FontSize = 9,
|
|
|
+ TextAlignment = TextAlignment.Center,
|
|
|
+ Width = 25
|
|
|
+ };
|
|
|
+ Canvas.SetLeft(number, 0);
|
|
|
+ Canvas.SetTop(number, -1);
|
|
|
+
|
|
|
+ foreach (var line in dimLines)
|
|
|
+ {
|
|
|
+ line.StrokeThickness = 2;
|
|
|
+ line.Stroke = new SolidColorBrush(Colors.Black);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var line in dotLines)
|
|
|
+ {
|
|
|
+ line.StrokeThickness = 1;
|
|
|
+ line.Stroke = new SolidColorBrush(Colors.Black);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bindColour)
|
|
|
+ {
|
|
|
+ foreach (var line in dimLines)
|
|
|
+ {
|
|
|
+ line.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush))
|
|
|
+ {
|
|
|
+ Source = this,
|
|
|
+ Converter = ImageEditorRemoveOpacityConverter.Instance
|
|
|
+ });
|
|
|
+ }
|
|
|
+ foreach (var line in dotLines)
|
|
|
+ {
|
|
|
+ line.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush))
|
|
|
+ {
|
|
|
+ Source = this,
|
|
|
+ Converter = ImageEditorRemoveOpacityConverter.Instance
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var line in dimLines)
|
|
|
+ {
|
|
|
+ canvas.Children.Add(line);
|
|
|
+ }
|
|
|
+ foreach (var line in dotLines)
|
|
|
+ {
|
|
|
+ canvas.Children.Add(line);
|
|
|
+ }
|
|
|
+ canvas.Children.Add(number);
|
|
|
+ }
|
|
|
+
|
|
|
+ return canvas;
|
|
|
default:
|
|
|
return null;
|
|
|
}
|
|
@@ -445,19 +582,53 @@ public partial class ImageEditor : UserControl
|
|
|
context.DrawImage(Source, new(0, 0, ImageWidth, ImageHeight));
|
|
|
}
|
|
|
|
|
|
+ CurrentObject = null;
|
|
|
+
|
|
|
foreach (var obj in Objects)
|
|
|
{
|
|
|
var control = obj.GetControl();
|
|
|
- var left = Canvas.GetLeft(control);
|
|
|
- var top = Canvas.GetTop(control);
|
|
|
- if (double.IsNaN(left)) left = 0;
|
|
|
- if (double.IsNaN(top)) top = 0;
|
|
|
- using (context.PushTransform(Matrix.CreateTranslation(new(left, top))))
|
|
|
+ Render(context, control);
|
|
|
+ }
|
|
|
+ return renderBitmap;
|
|
|
+ }
|
|
|
+ private void Render(DrawingContext context, Control control)
|
|
|
+ {
|
|
|
+ var left = Canvas.GetLeft(control);
|
|
|
+ var top = Canvas.GetTop(control);
|
|
|
+ if (double.IsNaN(left)) left = 0;
|
|
|
+ if (double.IsNaN(top)) top = 0;
|
|
|
+
|
|
|
+ var matrix = Matrix.CreateTranslation(new(left, top));
|
|
|
+
|
|
|
+ if(control.RenderTransform is not null)
|
|
|
+ {
|
|
|
+ Vector offset;
|
|
|
+ if(control.RenderTransformOrigin.Unit == RelativeUnit.Relative)
|
|
|
{
|
|
|
- control.Render(context);
|
|
|
+ offset = new Vector(
|
|
|
+ control.Bounds.Width * control.RenderTransformOrigin.Point.X,
|
|
|
+ control.Bounds.Height * control.RenderTransformOrigin.Point.Y);
|
|
|
}
|
|
|
+ else
|
|
|
+ {
|
|
|
+ offset = new Vector(control.RenderTransformOrigin.Point.X, control.RenderTransformOrigin.Point.Y);
|
|
|
+ }
|
|
|
+
|
|
|
+ matrix = (Matrix.CreateTranslation(-offset) * control.RenderTransform.Value * Matrix.CreateTranslation(offset)) * matrix;
|
|
|
}
|
|
|
- return renderBitmap;
|
|
|
+
|
|
|
+ using (context.PushTransform(matrix))
|
|
|
+ {
|
|
|
+ control.Render(context);
|
|
|
+ if(control is Panel panel)
|
|
|
+ {
|
|
|
+ foreach(var child in panel.Children)
|
|
|
+ {
|
|
|
+ Render(context, child);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
public byte[] SaveImage()
|
|
@@ -495,6 +666,7 @@ public partial class ImageEditor : UserControl
|
|
|
|
|
|
private void Canvas_PointerPressed(object? sender, PointerPressedEventArgs e)
|
|
|
{
|
|
|
+ CurrentObject = null;
|
|
|
var position = ConvertToImageCoordinates(e.GetPosition(Canvas));
|
|
|
switch (Mode)
|
|
|
{
|
|
@@ -538,6 +710,18 @@ public partial class ImageEditor : UserControl
|
|
|
};
|
|
|
AddObject(CurrentObject);
|
|
|
break;
|
|
|
+ case ImageEditingMode.Dimension:
|
|
|
+ CurrentObject = new DimensionObject
|
|
|
+ {
|
|
|
+ Point1 = position,
|
|
|
+ Point2 = position,
|
|
|
+ PrimaryBrush = PrimaryBrush,
|
|
|
+ Text = "",
|
|
|
+ Offset = 30,
|
|
|
+ LineThickness = LineThickness
|
|
|
+ };
|
|
|
+ AddObject(CurrentObject);
|
|
|
+ break;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -566,6 +750,14 @@ public partial class ImageEditor : UserControl
|
|
|
textSelection.Update();
|
|
|
Changed?.Invoke(this, new EventArgs());
|
|
|
break;
|
|
|
+ case DimensionObject dimension:
|
|
|
+ if (!dimension.Complete)
|
|
|
+ {
|
|
|
+ dimension.Point2 = position;
|
|
|
+ dimension.Update();
|
|
|
+ Changed?.Invoke(this, new EventArgs());
|
|
|
+ }
|
|
|
+ break;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -606,8 +798,24 @@ public partial class ImageEditor : UserControl
|
|
|
}
|
|
|
});
|
|
|
break;
|
|
|
- }
|
|
|
+ case DimensionObject dimension:
|
|
|
+ dimension.Point2 = position;
|
|
|
+ if(dimension.Point1 == dimension.Point2)
|
|
|
+ {
|
|
|
+ Objects.Remove(dimension);
|
|
|
+ CurrentObject = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ dimension.Complete = true;
|
|
|
|
|
|
+ Navigation.Popup<TextEditViewModel, string?>(x => { }).ContinueWith(task =>
|
|
|
+ {
|
|
|
+ dimension.Text = task.Result ?? "";
|
|
|
+ dimension.Update();
|
|
|
+ }, TaskScheduler.FromCurrentSynchronizationContext());
|
|
|
+ Changed?.Invoke(this, new EventArgs());
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private async Task CreateObjectFromSelection(SelectionObject selection)
|