ImageEditor.axaml.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Shapes;
  4. using Avalonia.Data;
  5. using Avalonia.Input;
  6. using Avalonia.Interactivity;
  7. using Avalonia.Markup.Xaml;
  8. using Avalonia.Media;
  9. using Avalonia.Media.Imaging;
  10. using Avalonia.Layout;
  11. using Avalonia.Skia.Helpers;
  12. using CommunityToolkit.Mvvm.Input;
  13. using FluentResults;
  14. using InABox.Avalonia.Components.ImageEditing;
  15. using InABox.Avalonia.Converters;
  16. using InABox.Core;
  17. using SkiaSharp;
  18. using System.Collections.ObjectModel;
  19. using System.Threading.Tasks;
  20. using Avalonia.LogicalTree;
  21. namespace InABox.Avalonia.Components;
  22. public enum ImageEditingMode
  23. {
  24. Polyline,
  25. Rectangle,
  26. Ellipse,
  27. Text,
  28. Dimension
  29. }
  30. public class ImageEditorModeButton(ImageEditingMode mode, Control? content)
  31. {
  32. public ImageEditingMode Mode { get; set; } = mode;
  33. public Control? Content { get; set; } = content;
  34. }
  35. public class ImageEditorTransparentImageBrushConverter : AbstractConverter<IBrush?, IBrush?>
  36. {
  37. public static readonly ImageEditorTransparentImageBrushConverter Instance = new ImageEditorTransparentImageBrushConverter();
  38. protected override IBrush? Convert(IBrush? value, object? parameter = null)
  39. {
  40. if (value is SolidColorBrush solid && solid.Color.A == 255) return solid;
  41. var brush = new VisualBrush
  42. {
  43. TileMode = TileMode.Tile,
  44. DestinationRect = new(0, 0, 10, 10, RelativeUnit.Absolute)
  45. };
  46. var canvas = new Canvas
  47. {
  48. Width = 10,
  49. Height = 10
  50. };
  51. var rect1 = new Rectangle { Width = 5, Height = 5 };
  52. var rect2 = new Rectangle { Width = 5, Height = 5 };
  53. Canvas.SetLeft(rect2, 5);
  54. Canvas.SetTop(rect2, 5);
  55. rect1.Fill = new SolidColorBrush(Colors.LightGray);
  56. rect2.Fill = new SolidColorBrush(Colors.LightGray);
  57. var rect3 = new Rectangle { Width = 10, Height = 10 };
  58. rect3.Fill = value;
  59. canvas.Children.Add(rect1);
  60. canvas.Children.Add(rect2);
  61. canvas.Children.Add(rect3);
  62. brush.Visual = canvas;
  63. return brush;
  64. }
  65. }
  66. public class ImageEditorRemoveOpacityConverter : AbstractConverter<IBrush?, IBrush?>
  67. {
  68. public static readonly ImageEditorRemoveOpacityConverter Instance = new();
  69. protected override IBrush? Convert(IBrush? value, object? parameter = null)
  70. {
  71. if (value is SolidColorBrush solid)
  72. {
  73. return new SolidColorBrush(new Color(255, solid.Color.R, solid.Color.G, solid.Color.B));
  74. }
  75. return value;
  76. }
  77. }
  78. // TODO: Make it so we don't re-render everything everytime 'Objects' changes.
  79. public partial class ImageEditor : UserControl
  80. {
  81. public static readonly StyledProperty<IImage?> SourceProperty =
  82. AvaloniaProperty.Register<ImageEditor, IImage?>(nameof(Source));
  83. public static readonly StyledProperty<IBrush?> PrimaryBrushProperty =
  84. AvaloniaProperty.Register<ImageEditor, IBrush?>(nameof(PrimaryBrush), new SolidColorBrush(Colors.Black));
  85. public static readonly StyledProperty<IBrush?> SecondaryBrushProperty =
  86. AvaloniaProperty.Register<ImageEditor, IBrush?>(nameof(SecondaryBrush), new SolidColorBrush(Colors.White));
  87. public static readonly StyledProperty<double> LineThicknessProperty =
  88. AvaloniaProperty.Register<ImageEditor, double>(nameof(LineThickness), 3.0);
  89. public static readonly StyledProperty<int> ImageWidthProperty =
  90. AvaloniaProperty.Register<ImageEditor, int>(nameof(ImageWidth), 100);
  91. public static readonly StyledProperty<int> ImageHeightProperty =
  92. AvaloniaProperty.Register<ImageEditor, int>(nameof(ImageHeight), 100);
  93. public static readonly StyledProperty<ImageEditingMode> ModeProperty =
  94. AvaloniaProperty.Register<ImageEditor, ImageEditingMode>(nameof(Mode), ImageEditingMode.Polyline);
  95. public static readonly StyledProperty<bool> ShowButtonsProperty =
  96. AvaloniaProperty.Register<ImageEditor, bool>(nameof(ShowButtons), true);
  97. public IImage? Source
  98. {
  99. get => GetValue(SourceProperty);
  100. set => SetValue(SourceProperty, value);
  101. }
  102. public int ImageWidth
  103. {
  104. get => GetValue(ImageWidthProperty);
  105. set => SetValue(ImageWidthProperty, value);
  106. }
  107. public int ImageHeight
  108. {
  109. get => GetValue(ImageHeightProperty);
  110. set => SetValue(ImageHeightProperty, value);
  111. }
  112. public bool ShowButtons
  113. {
  114. get => GetValue(ShowButtonsProperty);
  115. set => SetValue(ShowButtonsProperty, value);
  116. }
  117. #region Editing Properties
  118. public ImageEditingMode Mode
  119. {
  120. get => GetValue(ModeProperty);
  121. set => SetValue(ModeProperty, value);
  122. }
  123. public IBrush? PrimaryBrush
  124. {
  125. get => GetValue(PrimaryBrushProperty);
  126. set => SetValue(PrimaryBrushProperty, value);
  127. }
  128. public IBrush? SecondaryBrush
  129. {
  130. get => GetValue(SecondaryBrushProperty);
  131. set => SetValue(SecondaryBrushProperty, value);
  132. }
  133. public double LineThickness
  134. {
  135. get => GetValue(LineThicknessProperty);
  136. set => SetValue(LineThicknessProperty, value);
  137. }
  138. #endregion
  139. #region Events
  140. public event EventHandler? Changed;
  141. #endregion
  142. #region Private Properties
  143. public ObservableCollection<ImageEditorModeButton> ModeButtons { get; set; } = new();
  144. private ObservableCollection<IImageEditorObject> Objects = new();
  145. private IImageEditorObject? _currentObject;
  146. private IImageEditorObject? CurrentObject
  147. {
  148. get => _currentObject;
  149. set
  150. {
  151. _currentObject?.SetActive(false);
  152. _currentObject = value;
  153. }
  154. }
  155. private Stack<IImageEditorObject> RedoStack = new();
  156. private double ScaleFactor = 1.0;
  157. #endregion
  158. static ImageEditor()
  159. {
  160. SourceProperty.Changed.AddClassHandler<ImageEditor>(Source_Changed);
  161. }
  162. private static void Source_Changed(ImageEditor editor, AvaloniaPropertyChangedEventArgs args)
  163. {
  164. if(editor.Source is not null)
  165. {
  166. editor.ImageWidth = (int)Math.Floor(editor.Source.Size.Width);
  167. editor.ImageHeight = (int)Math.Floor(editor.Source.Size.Height);
  168. editor.PositionImage();
  169. }
  170. }
  171. public ImageEditor()
  172. {
  173. InitializeComponent();
  174. Objects.CollectionChanged += Objects_CollectionChanged;
  175. AddModeButtons();
  176. SetMode(Mode);
  177. OuterCanvas.LayoutUpdated += OuterCanvas_LayoutUpdated;
  178. }
  179. #region Layout
  180. private void OuterCanvas_LayoutUpdated(object? sender, EventArgs e)
  181. {
  182. PositionImage();
  183. }
  184. protected override void OnLoaded(RoutedEventArgs e)
  185. {
  186. base.OnLoaded(e);
  187. PositionImage();
  188. }
  189. private void PositionImage()
  190. {
  191. var canvasWidth = OuterCanvas.Bounds.Width;
  192. var canvasHeight = OuterCanvas.Bounds.Height;
  193. var scaleFactor = Math.Min(canvasWidth / ImageWidth, canvasHeight / ImageHeight);
  194. ScaleFactor = scaleFactor;
  195. var imageWidth = ImageWidth * scaleFactor;
  196. var imageHeight = ImageHeight * scaleFactor;
  197. ImageBorder.Width = imageWidth;
  198. ImageBorder.Height = imageHeight;
  199. Canvas.SetLeft(ImageBorder, OuterCanvas.Bounds.Width / 2 - imageWidth / 2);
  200. Canvas.SetTop(ImageBorder, OuterCanvas.Bounds.Height / 2 - imageHeight / 2);
  201. Canvas.RenderTransform = new ScaleTransform(ScaleFactor, ScaleFactor);
  202. Canvas.Width = ImageWidth;
  203. Canvas.Height = ImageHeight;
  204. }
  205. #endregion
  206. #region Editing Commands
  207. private void UpdateUndoRedoButtons()
  208. {
  209. UndoButton.IsEnabled = Objects.Count > 0;
  210. RedoButton.IsEnabled = RedoStack.Count > 0;
  211. }
  212. [RelayCommand]
  213. private void Undo()
  214. {
  215. if (Objects.Count == 0) return;
  216. RedoStack.Push(Objects[^1]);
  217. Objects.RemoveAt(Objects.Count - 1);
  218. UpdateUndoRedoButtons();
  219. Changed?.Invoke(this, new EventArgs());
  220. }
  221. [RelayCommand]
  222. private void Redo()
  223. {
  224. if (!RedoStack.TryPop(out var top)) return;
  225. Objects.Add(top);
  226. UpdateUndoRedoButtons();
  227. Changed?.Invoke(this, new EventArgs());
  228. }
  229. private void AddObject(IImageEditorObject obj)
  230. {
  231. Objects.Add(obj);
  232. RedoStack.Clear();
  233. UpdateUndoRedoButtons();
  234. Changed?.Invoke(this, new EventArgs());
  235. }
  236. [RelayCommand]
  237. private void SetMode(ImageEditingMode mode)
  238. {
  239. Mode = mode;
  240. ShapeButton.Content = CreateModeButtonContent(mode);
  241. SecondaryColour.IsVisible = HasSecondaryColour();
  242. LineThicknessButton.IsVisible = HasLineThickness();
  243. }
  244. private bool HasSecondaryColour()
  245. {
  246. return Mode == ImageEditingMode.Rectangle || Mode == ImageEditingMode.Ellipse;
  247. }
  248. private bool HasLineThickness()
  249. {
  250. return Mode == ImageEditingMode.Rectangle
  251. || Mode == ImageEditingMode.Ellipse
  252. || Mode == ImageEditingMode.Polyline
  253. || Mode == ImageEditingMode.Dimension;
  254. }
  255. #endregion
  256. #region Mode Buttons
  257. private void AddModeButtons()
  258. {
  259. AddModeButton(ImageEditingMode.Polyline);
  260. AddModeButton(ImageEditingMode.Rectangle);
  261. AddModeButton(ImageEditingMode.Ellipse);
  262. AddModeButton(ImageEditingMode.Text);
  263. AddModeButton(ImageEditingMode.Dimension);
  264. }
  265. private void AddModeButton(ImageEditingMode mode)
  266. {
  267. ModeButtons.Add(new(mode, CreateModeButtonContent(mode)));
  268. }
  269. private Control? CreateModeButtonContent(ImageEditingMode mode, bool bindColour = false)
  270. {
  271. switch (mode)
  272. {
  273. case ImageEditingMode.Polyline:
  274. var canvas = new Canvas();
  275. {
  276. var points = new Point[] { new(0, 0), new(20, 8), new(5, 16), new(25, 25) };
  277. var line1 = new Polyline { Points = points, Width = 25, Height = 25 };
  278. var line2 = new Polyline { Points = points, Width = 25, Height = 25 };
  279. line1.StrokeThickness = 4;
  280. line1.StrokeLineCap = PenLineCap.Round;
  281. line1.StrokeJoin = PenLineJoin.Round;
  282. line1.Stroke = new SolidColorBrush(Colors.Black);
  283. canvas.Children.Add(line1);
  284. if (bindColour)
  285. {
  286. line1.StrokeThickness = 5;
  287. line2.StrokeThickness = 4;
  288. line2.StrokeLineCap = PenLineCap.Round;
  289. line2.StrokeJoin = PenLineJoin.Round;
  290. line2.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush))
  291. {
  292. Source = this,
  293. Converter = ImageEditorRemoveOpacityConverter.Instance
  294. });
  295. canvas.Children.Add(line2);
  296. }
  297. }
  298. return canvas;
  299. case ImageEditingMode.Rectangle:
  300. canvas = new Canvas();
  301. canvas.Width = 25;
  302. canvas.Height = 25;
  303. var rectangle = new Rectangle();
  304. if (bindColour)
  305. {
  306. rectangle.Bind(Rectangle.StrokeProperty, new Binding(nameof(PrimaryBrush))
  307. {
  308. Source = this,
  309. Converter = ImageEditorRemoveOpacityConverter.Instance
  310. });
  311. rectangle.Bind(Rectangle.FillProperty, new Binding(nameof(SecondaryBrush))
  312. {
  313. Source = this,
  314. Converter = ImageEditorTransparentImageBrushConverter.Instance
  315. });
  316. }
  317. else
  318. {
  319. rectangle.Stroke = new SolidColorBrush(Colors.Black);
  320. rectangle.Fill = new SolidColorBrush(Colors.White);
  321. }
  322. rectangle.StrokeThickness = 1.0;
  323. rectangle.Width = 25;
  324. rectangle.Height = 25;
  325. canvas.Children.Add(rectangle);
  326. return canvas;
  327. case ImageEditingMode.Ellipse:
  328. canvas = new Canvas();
  329. canvas.Width = 25;
  330. canvas.Height = 25;
  331. var ellipse = new Ellipse();
  332. if (bindColour)
  333. {
  334. ellipse.Bind(Rectangle.StrokeProperty, new Binding(nameof(PrimaryBrush))
  335. {
  336. Source = this,
  337. Converter = ImageEditorRemoveOpacityConverter.Instance
  338. });
  339. ellipse.Bind(Rectangle.FillProperty, new Binding(nameof(SecondaryBrush))
  340. {
  341. Source = this,
  342. Converter = ImageEditorTransparentImageBrushConverter.Instance
  343. });
  344. }
  345. else
  346. {
  347. ellipse.Stroke = new SolidColorBrush(Colors.Black);
  348. ellipse.Fill = new SolidColorBrush(Colors.White);
  349. }
  350. ellipse.StrokeThickness = 1.0;
  351. ellipse.Width = 25;
  352. ellipse.Height = 25;
  353. canvas.Children.Add(ellipse);
  354. return canvas;
  355. case ImageEditingMode.Text:
  356. var textBox = new TextBlock();
  357. textBox.Text = "T";
  358. textBox.FontSize = 25;
  359. textBox.TextAlignment = TextAlignment.Center;
  360. textBox.HorizontalAlignment = HorizontalAlignment.Center;
  361. textBox.VerticalAlignment = VerticalAlignment.Center;
  362. if (bindColour)
  363. {
  364. textBox.Bind(TextBlock.ForegroundProperty, new Binding(nameof(PrimaryBrush))
  365. {
  366. Source = this,
  367. Converter = ImageEditorRemoveOpacityConverter.Instance
  368. });
  369. }
  370. return textBox;
  371. case ImageEditingMode.Dimension:
  372. canvas = new Canvas();
  373. canvas.Width = 25;
  374. canvas.Height = 25;
  375. {
  376. var dimLines = new List<Line>();
  377. dimLines.Add(new Line
  378. {
  379. StartPoint = new(2, 10),
  380. EndPoint = new(23, 10),
  381. StrokeLineCap = PenLineCap.Round
  382. });
  383. dimLines.Add(new Line
  384. {
  385. StartPoint = new(2, 10),
  386. EndPoint = new(5, 7),
  387. StrokeLineCap = PenLineCap.Square
  388. });
  389. dimLines.Add(new Line
  390. {
  391. StartPoint = new(2, 10),
  392. EndPoint = new(5, 13),
  393. StrokeLineCap = PenLineCap.Square
  394. });
  395. dimLines.Add(new Line
  396. {
  397. StartPoint = new(23, 10),
  398. EndPoint = new(20, 7),
  399. StrokeLineCap = PenLineCap.Square
  400. });
  401. dimLines.Add(new Line
  402. {
  403. StartPoint = new(23, 10),
  404. EndPoint = new(20, 13),
  405. StrokeLineCap = PenLineCap.Square
  406. });
  407. var dotLines = new List<Line>();
  408. dotLines.Add(new Line
  409. {
  410. StartPoint = new(2, 10),
  411. EndPoint = new(2, 24),
  412. StrokeDashArray = [2, 2]
  413. });
  414. dotLines.Add(new Line
  415. {
  416. StartPoint = new(23, 10),
  417. EndPoint = new(23, 24),
  418. StrokeDashArray = [2, 2]
  419. });
  420. var number = new TextBlock
  421. {
  422. Text = "10",
  423. FontSize = 9,
  424. TextAlignment = TextAlignment.Center,
  425. Width = 25
  426. };
  427. Canvas.SetLeft(number, 0);
  428. Canvas.SetTop(number, -1);
  429. foreach (var line in dimLines)
  430. {
  431. line.StrokeThickness = 2;
  432. line.Stroke = new SolidColorBrush(Colors.Black);
  433. }
  434. foreach (var line in dotLines)
  435. {
  436. line.StrokeThickness = 1;
  437. line.Stroke = new SolidColorBrush(Colors.Black);
  438. }
  439. if (bindColour)
  440. {
  441. foreach (var line in dimLines)
  442. {
  443. line.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush))
  444. {
  445. Source = this,
  446. Converter = ImageEditorRemoveOpacityConverter.Instance
  447. });
  448. }
  449. foreach (var line in dotLines)
  450. {
  451. line.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush))
  452. {
  453. Source = this,
  454. Converter = ImageEditorRemoveOpacityConverter.Instance
  455. });
  456. }
  457. }
  458. foreach (var line in dimLines)
  459. {
  460. canvas.Children.Add(line);
  461. }
  462. foreach (var line in dotLines)
  463. {
  464. canvas.Children.Add(line);
  465. }
  466. canvas.Children.Add(number);
  467. }
  468. return canvas;
  469. default:
  470. return null;
  471. }
  472. }
  473. #endregion
  474. #region Public Interface
  475. public void Reset()
  476. {
  477. Objects.Clear();
  478. RedoStack.Clear();
  479. UpdateUndoRedoButtons();
  480. Changed?.Invoke(this, new EventArgs());
  481. }
  482. public Bitmap GetImage()
  483. {
  484. var renderBitmap = new RenderTargetBitmap(new PixelSize(ImageWidth, ImageHeight));
  485. renderBitmap.Render(Image);
  486. using var context = renderBitmap.CreateDrawingContext();
  487. if(Source is not null)
  488. {
  489. context.DrawImage(Source, new(0, 0, ImageWidth, ImageHeight));
  490. }
  491. CurrentObject = null;
  492. foreach (var obj in Objects)
  493. {
  494. var control = obj.GetControl();
  495. Render(context, control);
  496. }
  497. return renderBitmap;
  498. }
  499. private void Render(DrawingContext context, Control control)
  500. {
  501. var left = Canvas.GetLeft(control);
  502. var top = Canvas.GetTop(control);
  503. if (double.IsNaN(left)) left = 0;
  504. if (double.IsNaN(top)) top = 0;
  505. var matrix = Matrix.CreateTranslation(new(left, top));
  506. if(control.RenderTransform is not null)
  507. {
  508. Vector offset;
  509. if(control.RenderTransformOrigin.Unit == RelativeUnit.Relative)
  510. {
  511. offset = new Vector(
  512. control.Bounds.Width * control.RenderTransformOrigin.Point.X,
  513. control.Bounds.Height * control.RenderTransformOrigin.Point.Y);
  514. }
  515. else
  516. {
  517. offset = new Vector(control.RenderTransformOrigin.Point.X, control.RenderTransformOrigin.Point.Y);
  518. }
  519. matrix = (Matrix.CreateTranslation(-offset) * control.RenderTransform.Value * Matrix.CreateTranslation(offset)) * matrix;
  520. }
  521. using (context.PushTransform(matrix))
  522. {
  523. control.Render(context);
  524. if(control is Panel panel)
  525. {
  526. foreach(var child in panel.Children)
  527. {
  528. Render(context, child);
  529. }
  530. }
  531. }
  532. }
  533. public byte[] SaveImage()
  534. {
  535. var bitmap = GetImage();
  536. var stream = new MemoryStream();
  537. bitmap.Save(stream);
  538. return stream.ToArray();
  539. }
  540. #endregion
  541. #region Editing
  542. private void RefreshObjects()
  543. {
  544. Canvas.Children.Clear();
  545. foreach(var item in Objects)
  546. {
  547. item.Update();
  548. Canvas.Children.Add(item.GetControl());
  549. }
  550. }
  551. private void Objects_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  552. {
  553. RefreshObjects();
  554. }
  555. Point ConvertToImageCoordinates(Point canvasCoordinates)
  556. {
  557. return canvasCoordinates;// new(canvasCoordinates.X / ScaleFactor, canvasCoordinates.Y / ScaleFactor);
  558. }
  559. private void Canvas_PointerPressed(object? sender, PointerPressedEventArgs e)
  560. {
  561. CurrentObject = null;
  562. var position = ConvertToImageCoordinates(e.GetPosition(Canvas));
  563. switch (Mode)
  564. {
  565. case ImageEditingMode.Polyline:
  566. CurrentObject = new PolylineObject
  567. {
  568. Points = [position],
  569. PrimaryBrush = PrimaryBrush,
  570. Thickness = LineThickness
  571. };
  572. AddObject(CurrentObject);
  573. break;
  574. case ImageEditingMode.Rectangle:
  575. CurrentObject = new RectangleObject
  576. {
  577. Point1 = position,
  578. Point2 = position,
  579. PrimaryBrush = PrimaryBrush,
  580. SecondaryBrush = SecondaryBrush,
  581. Thickness = LineThickness
  582. };
  583. AddObject(CurrentObject);
  584. break;
  585. case ImageEditingMode.Ellipse:
  586. CurrentObject = new EllipseObject
  587. {
  588. Point1 = position,
  589. Point2 = position,
  590. PrimaryBrush = PrimaryBrush,
  591. SecondaryBrush = SecondaryBrush,
  592. Thickness = LineThickness
  593. };
  594. AddObject(CurrentObject);
  595. break;
  596. case ImageEditingMode.Text:
  597. CurrentObject = new SelectionObject
  598. {
  599. Point1 = position,
  600. Point2 = position,
  601. PrimaryBrush = PrimaryBrush
  602. };
  603. AddObject(CurrentObject);
  604. break;
  605. case ImageEditingMode.Dimension:
  606. CurrentObject = new DimensionObject
  607. {
  608. Point1 = position,
  609. Point2 = position,
  610. PrimaryBrush = PrimaryBrush,
  611. Text = "",
  612. Offset = 30,
  613. LineThickness = LineThickness
  614. };
  615. AddObject(CurrentObject);
  616. break;
  617. }
  618. }
  619. private void Canvas_PointerMoved(object? sender, PointerEventArgs e)
  620. {
  621. var position = ConvertToImageCoordinates(e.GetPosition(Canvas));
  622. switch (CurrentObject)
  623. {
  624. case PolylineObject polyline:
  625. polyline.Points.Add(position);
  626. polyline.Update();
  627. Changed?.Invoke(this, new EventArgs());
  628. break;
  629. case RectangleObject rectangle:
  630. rectangle.Point2 = position;
  631. rectangle.Update();
  632. Changed?.Invoke(this, new EventArgs());
  633. break;
  634. case EllipseObject ellipse:
  635. ellipse.Point2 = position;
  636. ellipse.Update();
  637. Changed?.Invoke(this, new EventArgs());
  638. break;
  639. case SelectionObject textSelection:
  640. textSelection.Point2 = position;
  641. textSelection.Update();
  642. Changed?.Invoke(this, new EventArgs());
  643. break;
  644. case DimensionObject dimension:
  645. if (!dimension.Complete)
  646. {
  647. dimension.Point2 = position;
  648. dimension.Update();
  649. Changed?.Invoke(this, new EventArgs());
  650. }
  651. break;
  652. }
  653. }
  654. private void Canvas_PointerReleased(object? sender, PointerReleasedEventArgs e)
  655. {
  656. var position = ConvertToImageCoordinates(e.GetPosition(Canvas));
  657. switch (CurrentObject)
  658. {
  659. case PolylineObject polyline:
  660. polyline.Points.Add(position);
  661. polyline.Update();
  662. CurrentObject = null;
  663. Changed?.Invoke(this, new EventArgs());
  664. break;
  665. case RectangleObject rectangle:
  666. rectangle.Point2 = position;
  667. rectangle.Update();
  668. CurrentObject = null;
  669. Changed?.Invoke(this, new EventArgs());
  670. break;
  671. case EllipseObject ellipse:
  672. ellipse.Point2 = position;
  673. ellipse.Update();
  674. CurrentObject = null;
  675. Changed?.Invoke(this, new EventArgs());
  676. break;
  677. case SelectionObject selection:
  678. selection.Point2 = position;
  679. Objects.Remove(selection);
  680. CurrentObject = null;
  681. CreateObjectFromSelection(selection).ContinueWith(task =>
  682. {
  683. if(task.Exception != null)
  684. {
  685. MobileLogging.LogExceptionMessage(task.Exception);
  686. }
  687. });
  688. break;
  689. case DimensionObject dimension:
  690. dimension.Point2 = position;
  691. if(dimension.Point1 == dimension.Point2)
  692. {
  693. Objects.Remove(dimension);
  694. CurrentObject = null;
  695. return;
  696. }
  697. dimension.Complete = true;
  698. Navigation.Popup<TextEditViewModel, string?>(x => { }).ContinueWith(task =>
  699. {
  700. dimension.Text = task.Result ?? "";
  701. dimension.Update();
  702. }, TaskScheduler.FromCurrentSynchronizationContext());
  703. Changed?.Invoke(this, new EventArgs());
  704. break;
  705. }
  706. }
  707. private async Task CreateObjectFromSelection(SelectionObject selection)
  708. {
  709. switch (Mode)
  710. {
  711. case ImageEditingMode.Text:
  712. var text = await Navigation.Popup<TextEditViewModel, string?>(x => { });
  713. if(text is null)
  714. {
  715. return;
  716. }
  717. CurrentObject = new TextObject
  718. {
  719. Point = selection.GetTopLeft(),
  720. Size = selection.GetSize(),
  721. Text = text,
  722. PrimaryBrush = selection.PrimaryBrush
  723. };
  724. AddObject(CurrentObject);
  725. break;
  726. }
  727. }
  728. #endregion
  729. }