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); }
}
///
/// Rect / Arrow / Text / Select
///
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>(_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().FirstOrDefault();
var head = _lastArrowGroup.Children.OfType().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(string path, T data)
{
var ser = new XmlSerializer(typeof(T));
using (var fs = File.Create(path))
{
ser.Serialize(fs, data);
}
}
private T ReadXml(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 DumpAnnoItems()
{
var list = new List();
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().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;
}
}
}
}