| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- using Microsoft.Win32;
- using Prism.Commands;
- using Prism.Mvvm;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- using System.Windows.Shapes;
- using System.Xml.Serialization;
- using TeamAAS_VP.Views.Dialogs;
- namespace TeamAAS_VP.ViewModels
- {
- public class ImageViewModel : BindableBase
- {
- private const string AppFolderName = "TeamAAS-VM";
- private const string ImageFileName = "LastImage.png";
- private const string XmlFileName = "LastAnno.xml";
- private string _folder;
- private string _imagePath;
- private string _xmlPath;
- private FrameworkElement _renderTarget;
- private Canvas _canvas;
- private Grid _lastArrowGroup;
- public ImageViewModel()
- {
- InitPaths();
- ImportImageCommand = new DelegateCommand(ImportImage);
- SaveCommand = new DelegateCommand(Save);
- ClearCommand = new DelegateCommand(Clear);
- Tool = "Rect";
- ColorName = "Red";
- Thickness = 2;
- }
- #region Commands
- public DelegateCommand ImportImageCommand { get; }
- public DelegateCommand SaveCommand { get; }
- public DelegateCommand ClearCommand { get; }
- #endregion
- #region Bindable Properties
- private ImageSource _ImageSource;
- public ImageSource ImageSource
- {
- get { return _ImageSource; }
- set { SetProperty(ref _ImageSource, value); }
- }
- private int _PixelWidth;
- public int PixelWidth
- {
- get { return _PixelWidth; }
- set { SetProperty(ref _PixelWidth, value); }
- }
- private int _PixelHeight;
- public int PixelHeight
- {
- get { return _PixelHeight; }
- set { SetProperty(ref _PixelHeight, value); }
- }
- /// <summary>
- /// Rect / Arrow / Text / Select
- /// </summary>
- private string _Tool;
- public string Tool
- {
- get { return _Tool; }
- set { SetProperty(ref _Tool, value); }
- }
- private string _ColorName;
- public string ColorName
- {
- get { return _ColorName; }
- set { SetProperty(ref _ColorName, value); }
- }
- private double _Thickness;
- public double Thickness
- {
- get { return _Thickness; }
- set { SetProperty(ref _Thickness, value); }
- }
- #endregion
- #region Public API (called from ImageView.xaml.cs)
- public void AttachRenderTarget(FrameworkElement renderTarget, Canvas canvas)
- {
- _renderTarget = renderTarget;
- _canvas = canvas;
- }
- public void LoadLast()
- {
- try
- {
- if (File.Exists(_imagePath))
- {
- var bmp = LoadBitmap(_imagePath);
- ImageSource = bmp;
- PixelWidth = bmp.PixelWidth;
- PixelHeight = bmp.PixelHeight;
- }
- if (_canvas != null && File.Exists(_xmlPath))
- {
- var list = ReadXml<List<AnnoItem>>(_xmlPath);
- if (list != null)
- {
- _canvas.Children.Clear();
- foreach (var item in list)
- {
- RestoreItem(item);
- }
- }
- }
- }
- catch
- {
- }
- }
- public Rectangle CreateRect()
- {
- return new Rectangle
- {
- Stroke = GetStrokeBrush(),
- StrokeThickness = Thickness,
- Fill = Brushes.Transparent
- };
- }
- public void CreateTextAt(Canvas canvas, Point pixelPoint)
- {
- var dlg = new TextInputDialog();
- dlg.Owner = Application.Current?.MainWindow;
- if (dlg.ShowDialog() != true) return;
- var input = dlg.InputText;
- if (string.IsNullOrWhiteSpace(input)) return;
- var tb = new TextBlock
- {
- Text = input,
- Foreground = GetStrokeBrush(),
- FontSize = 20,
- Background = Brushes.Transparent
- };
- Canvas.SetLeft(tb, pixelPoint.X);
- Canvas.SetTop(tb, pixelPoint.Y);
- canvas.Children.Add(tb);
- }
- public Grid CreateArrowGroup(Point start, Point end)
- {
- var brush = GetStrokeBrush();
- var line = new Line
- {
- X1 = start.X,
- Y1 = start.Y,
- X2 = end.X,
- Y2 = end.Y,
- Stroke = brush,
- StrokeThickness = Thickness
- };
- var head = new Polygon
- {
- Fill = brush,
- Stroke = brush,
- StrokeThickness = 1
- };
- var group = new Grid();
- group.Children.Add(line);
- group.Children.Add(head);
- group.Tag = line;
- _lastArrowGroup = group;
- UpdateArrowHead(line, head);
- return group;
- }
- public void UpdateLastArrow(Canvas canvas, Point current)
- {
- if (_lastArrowGroup == null) return;
- var line = _lastArrowGroup.Children.OfType<Line>().FirstOrDefault();
- var head = _lastArrowGroup.Children.OfType<Polygon>().FirstOrDefault();
- if (line == null || head == null) return;
- line.X2 = current.X;
- line.Y2 = current.Y;
- UpdateArrowHead(line, head);
- }
- public void CommitLastArrow(Canvas canvas, Point end)
- {
- UpdateLastArrow(canvas, end);
- }
- #endregion
- #region Command handlers
- private void ImportImage()
- {
- var dlg = new OpenFileDialog();
- dlg.Title = "导入图片";
- dlg.Filter = "Image Files|*.png;*.jpg;*.jpeg;*.bmp|All Files|*.*";
- if (dlg.ShowDialog() != true) return;
- var bmp = LoadBitmap(dlg.FileName);
- ImageSource = bmp;
- PixelWidth = bmp.PixelWidth;
- PixelHeight = bmp.PixelHeight;
- _canvas?.Children.Clear();
- _lastArrowGroup = null;
- }
- private void Clear()
- {
- _canvas?.Children.Clear();
- _lastArrowGroup = null;
- }
- private void Save()
- {
- if (ImageSource == null)
- {
- MessageBox.Show("请先导入图片", "AAS", MessageBoxButton.OK, MessageBoxImage.Asterisk);
- return;
- }
- if (_renderTarget == null || _canvas == null)
- {
- MessageBox.Show("保存失败:界面尚未初始化完成", "AAS", MessageBoxButton.OK, MessageBoxImage.Asterisk);
- return;
- }
- if (PixelWidth <= 0 || PixelHeight <= 0)
- {
- MessageBox.Show("图片像素尺寸无效", "AAS", MessageBoxButton.OK, MessageBoxImage.Asterisk);
- return;
- }
- try
- {
- var merged = RenderPixelElement(_renderTarget, PixelWidth, PixelHeight);
- SavePng(merged, _imagePath);
- var items = DumpAnnoItems();
- WriteXml(_xmlPath, items);
- MessageBox.Show("保存成功", "AAS", MessageBoxButton.OK, MessageBoxImage.Asterisk);
- }
- catch (Exception ex)
- {
- MessageBox.Show(ex.Message, "AAS", MessageBoxButton.OK, MessageBoxImage.Asterisk);
- }
- }
- #endregion
- #region Persistence (PNG + XML)
- private void InitPaths()
- {
- _folder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppFolderName);
- Directory.CreateDirectory(_folder);
- _imagePath = System.IO.Path.Combine(_folder, ImageFileName);
- _xmlPath = System.IO.Path.Combine(_folder, XmlFileName);
- }
- private BitmapImage LoadBitmap(string path)
- {
- var bmp = new BitmapImage();
- bmp.BeginInit();
- bmp.CacheOption = BitmapCacheOption.OnLoad;
- bmp.UriSource = new Uri(path, UriKind.Absolute);
- bmp.EndInit();
- bmp.Freeze();
- return bmp;
- }
- private BitmapSource RenderPixelElement(FrameworkElement element, int pixelWidth, int pixelHeight)
- {
- element.Measure(new Size(pixelWidth, pixelHeight));
- element.Arrange(new Rect(0, 0, pixelWidth, pixelHeight));
- element.UpdateLayout();
- var rtb = new RenderTargetBitmap(pixelWidth, pixelHeight, 96, 96, PixelFormats.Pbgra32);
- rtb.Render(element);
- rtb.Freeze();
- return rtb;
- }
- private void SavePng(BitmapSource source, string path)
- {
- var encoder = new PngBitmapEncoder();
- encoder.Frames.Add(BitmapFrame.Create(source));
- using (var fs = File.Create(path))
- {
- encoder.Save(fs);
- }
- }
- private void WriteXml<T>(string path, T data)
- {
- var ser = new XmlSerializer(typeof(T));
- using (var fs = File.Create(path))
- {
- ser.Serialize(fs, data);
- }
- }
- private T ReadXml<T>(string path) where T : class
- {
- var ser = new XmlSerializer(typeof(T));
- using (var fs = File.OpenRead(path))
- {
- return ser.Deserialize(fs) as T;
- }
- }
- #endregion
- #region Dump/Restore items
- private List<AnnoItem> DumpAnnoItems()
- {
- var list = new List<AnnoItem>();
- if (_canvas == null) return list;
- foreach (var child in _canvas.Children)
- {
- if (child is Rectangle r)
- {
- list.Add(new AnnoItem
- {
- Type = "Rect",
- X = SafeGetLeft(r),
- Y = SafeGetTop(r),
- W = r.Width,
- H = r.Height,
- Color = BrushToColorString(r.Stroke),
- Thickness = r.StrokeThickness
- });
- }
- else if (child is TextBlock t)
- {
- list.Add(new AnnoItem
- {
- Type = "Text",
- X = SafeGetLeft(t),
- Y = SafeGetTop(t),
- Text = t.Text,
- Color = BrushToColorString(t.Foreground),
- FontSize = t.FontSize
- });
- }
- else if (child is Grid g)
- {
- var line = g.Children.OfType<Line>().FirstOrDefault();
- if (line != null)
- {
- list.Add(new AnnoItem
- {
- Type = "Arrow",
- X1 = line.X1,
- Y1 = line.Y1,
- X2 = line.X2,
- Y2 = line.Y2,
- Color = BrushToColorString(line.Stroke),
- Thickness = line.StrokeThickness
- });
- }
- }
- }
- return list;
- }
- private void RestoreItem(AnnoItem item)
- {
- if (_canvas == null || item == null) return;
- if (item.Type == "Rect")
- {
- var rect = new Rectangle
- {
- Stroke = BrushFrom(item.Color),
- StrokeThickness = item.Thickness <= 0 ? 2 : item.Thickness,
- Fill = Brushes.Transparent,
- Width = item.W,
- Height = item.H
- };
- Canvas.SetLeft(rect, item.X);
- Canvas.SetTop(rect, item.Y);
- _canvas.Children.Add(rect);
- return;
- }
- if (item.Type == "Text")
- {
- var tb = new TextBlock
- {
- Text = item.Text ?? string.Empty,
- Foreground = BrushFrom(item.Color),
- FontSize = item.FontSize <= 0 ? 20 : item.FontSize,
- Background = Brushes.Transparent
- };
- Canvas.SetLeft(tb, item.X);
- Canvas.SetTop(tb, item.Y);
- _canvas.Children.Add(tb);
- return;
- }
- if (item.Type == "Arrow")
- {
- var brush = BrushFrom(item.Color);
- var line = new Line
- {
- X1 = item.X1,
- Y1 = item.Y1,
- X2 = item.X2,
- Y2 = item.Y2,
- Stroke = brush,
- StrokeThickness = item.Thickness <= 0 ? 2 : item.Thickness
- };
- var head = new Polygon
- {
- Fill = brush,
- Stroke = brush,
- StrokeThickness = 1
- };
- var group = new Grid();
- group.Children.Add(line);
- group.Children.Add(head);
- group.Tag = line;
- UpdateArrowHead(line, head);
- _canvas.Children.Add(group);
- }
- }
- #endregion
- #region Helpers
- private Brush GetStrokeBrush()
- {
- try
- {
- var obj = ColorConverter.ConvertFromString(ColorName);
- if (obj != null)
- {
- return new SolidColorBrush((Color)obj);
- }
- }
- catch { }
- return Brushes.Red;
- }
- private Brush BrushFrom(string color)
- {
- try
- {
- if (!string.IsNullOrWhiteSpace(color))
- {
- var c = (Color)ColorConverter.ConvertFromString(color);
- return new SolidColorBrush(c);
- }
- }
- catch { }
- return Brushes.Red;
- }
- private string BrushToColorString(Brush brush)
- {
- if (brush is SolidColorBrush s)
- {
- return s.Color.ToString();
- }
- return Colors.Red.ToString();
- }
- private double SafeGetLeft(UIElement e)
- {
- var v = Canvas.GetLeft(e);
- return double.IsNaN(v) ? 0 : v;
- }
- private double SafeGetTop(UIElement e)
- {
- var v = Canvas.GetTop(e);
- return double.IsNaN(v) ? 0 : v;
- }
- private void UpdateArrowHead(Line line, Polygon head)
- {
- var dx = line.X2 - line.X1;
- var dy = line.Y2 - line.Y1;
- var len = Math.Sqrt(dx * dx + dy * dy);
- if (len < 1) len = 1;
- var ux = dx / len;
- var uy = dy / len;
- const double size = 12.0;
- var px = -uy;
- var py = ux;
- var tip = new Point(line.X2, line.Y2);
- var p1 = new Point(line.X2 - ux * size + px * (size / 2), line.Y2 - uy * size + py * (size / 2));
- var p2 = new Point(line.X2 - ux * size - px * (size / 2), line.Y2 - uy * size - py * (size / 2));
- head.Points.Clear();
- head.Points.Add(tip);
- head.Points.Add(p1);
- head.Points.Add(p2);
- }
- #endregion
- [Serializable]
- public class AnnoItem
- {
- public string Type { get; set; }
- public double X { get; set; }
- public double Y { get; set; }
- public double W { get; set; }
- public double H { get; set; }
- public string Text { get; set; }
- public double FontSize { get; set; }
- public double X1 { get; set; }
- public double Y1 { get; set; }
- public double X2 { get; set; }
- public double Y2 { get; set; }
- public string Color { get; set; }
- public double Thickness { get; set; }
- }
- public void RemoveAnnotation(UIElement element)
- {
- if (element == null) return;
- if (_canvas == null) return;
- if (_canvas.Children.Contains(element))
- {
- _canvas.Children.Remove(element);
- }
- // 如果删的是最后一根箭头,清理引用避免后续更新异常
- if (ReferenceEquals(_lastArrowGroup, element))
- {
- _lastArrowGroup = null;
- }
- }
- }
- }
|