效果如图

实现了绘图,自适应缩放

核心代码如下

    <Window.InputBindings>
<KeyBinding Key="Z" Modifiers="Ctrl" Command="{Binding UndoCommand}" />
</Window.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SizeChanged">
<i:InvokeCommandAction Command="{Binding SizeChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid Background="AliceBlue">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.35*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,0,0" Grid.ColumnSpan="2">
<!--<Button Content="+" Command="{Binding DrawRectCommand}" />-->
<Button Content=" + " Margin="10,0,0,0" Command="{Binding CreateCommand}" />
<Button Content="Save" Margin="10,0,0,0" Command="{Binding SaveCommand}" />
<Button Content="Clear" Margin="10,0,0,0" Command="{Binding ClearCommand}" />
<Button Content="Cancel" Margin="10,0,0,0" Command="{Binding UndoCommand}" />
<Button Content="Refresh" Margin="10,0,0,0" Command="{Binding RefreshCommand}" />
</StackPanel> <StackPanel Grid.Row="1" Grid.Column="0">
<ListBox SelectedItem="{Binding SelectedSlide}">
<ListBox.Resources>
<CollectionViewSource x:Key="SlideControlParams" Source="{Binding SlideControlParams}" />
</ListBox.Resources>
<ListBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource SlideControlParams}}" />
</CompositeCollection>
</ListBox.ItemsSource> <ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle> <ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="40" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions> <Grid.InputBindings>
<MouseBinding Command="{Binding DataContext.OnListViewItemDoubleClick, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
MouseAction="LeftDoubleClick" CommandParameter="{Binding }" />
</Grid.InputBindings>
<TextBlock Text="{Binding Path= Name}"> <TextBlock.VerticalAlignment>Center</TextBlock.VerticalAlignment>
<TextBlock.Margin>5,0,0,0</TextBlock.Margin>
</TextBlock>
<TextBox Text="{Binding Name,Mode=TwoWay}" VerticalAlignment="Center" Margin="5,0,0,0" Visibility="{Binding EditStatus}" /> <Button Content="X" Grid.Column="1" Width="40" VerticalAlignment="Center" Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" CommandParameter="{Binding }" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel> <Image Source="{Binding SelectedSlide.ImagePath}" Grid.Row="1" x:Name="imgSlide" Grid.Column="1">
</Image> <Canvas Grid.Row="1" Name="canvas" Background="#19DAB1B1" Grid.Column="1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<cmd:EventToCommand Command="{Binding CmdLeftMouseDown}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeftButtonUp">
<cmd:EventToCommand Command="{Binding CmdLeftMouseUp}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseMove">
<cmd:EventToCommand Command="{Binding CmdLeftMouseMove}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseRightButtonUp">
<cmd:EventToCommand Command="{Binding MouseRightButtonUp}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</Grid>
</Grid>
</Window>
    public class UCP16ConfigVM : ViewModelBase
{
public UCP16ConfigVM(Canvas _canvas, Image _imgSlide)
{
canvas = _canvas;
imgSlide = _imgSlide;
InitEnv();
InitCommand();
LoadConfig();
string rootPath = System.Environment.CurrentDirectory;
jsonPath = System.IO.Path.Combine(rootPath, "ScanPara", "slideParms.json");
imgDir = System.IO.Path.Combine(rootPath, "ScanPara", "SlideConfigImages");
if (!Directory.Exists(imgDir))
{
Directory.CreateDirectory(imgDir);
}
if (!File.Exists(jsonPath))
{
SlideP16Parms prams = new SlideP16Parms();
var str = JsonConvert.SerializeObject(prams, Formatting.Indented);
File.WriteAllText(jsonPath, str);
}
} #region private param /// <summary>
/// 是否正在绘图状态
/// </summary>
private bool isDrawing = false; /// <summary>
/// 起始点
/// </summary>
private Point startPoint; /// <summary>
/// 结束点
/// </summary>
private Point endPoint; /// <summary>
/// canvas控件
/// </summary>
private Canvas canvas = null; /// <summary>
/// image控件
/// </summary>
private Image imgSlide = null; /// <summary>
/// 颜色刷
/// </summary>
private Brush brushDanger; /// <summary>
/// rect效果
/// </summary>
private DropShadowEffect shadowEffect; /// <summary>
/// 临时矩形
/// </summary>
private Rectangle tempRect = null; private readonly string jsonPath;
private readonly string imgDir; //图像控件相对于canva的坐标
private Point imgMarginPos; #endregion private param #region ViewModels private ObservableCollection<SlideP16ItemCombine> _SlideControlParams; /// <summary>
/// 控件对应的坐标
/// </summary>
public ObservableCollection<SlideP16ItemCombine> SlideControlParams
{
get => _SlideControlParams;
set { Set(ref _SlideControlParams, value); }
} ///// <summary>
///// 图像坐标,需要同步更新
///// </summary>
//public List<SlideP16Item> SlideImageParams { get; set; } private SlideP16ItemCombine _SelectedSlide; public SlideP16ItemCombine SelectedSlide
{
get => _SelectedSlide;
set
{
if (value != _SelectedSlide)
{
Set(ref _SelectedSlide, value);
foreach (var item in SlideControlParams)
{
if (item != value)
item.EditStatus = Visibility.Collapsed;
}
LoadPoints();
}
}
} #endregion ViewModels #region Command /// <summary>
/// 清空所有绘图
/// </summary>
public RelayCommand ClearCommand { get; private set; } /// <summary>
/// 取消,撤销上一步操作
/// </summary>
public RelayCommand UndoCommand { get; private set; } public RelayCommand<MouseEventArgs> CmdLeftMouseDown { get; private set; }
public RelayCommand<MouseEventArgs> CmdLeftMouseMove { get; private set; }
public RelayCommand<MouseEventArgs> CmdLeftMouseUp { get; private set; }
public RelayCommand<MouseEventArgs> MouseRightButtonUp { get; private set; } public RelayCommand<SlideP16ItemCombine> OnListViewItemDoubleClick { get; private set; } /// <summary>
/// 刷新数据
/// </summary>
public RelayCommand RefreshCommand { get; private set; } /// <summary>
/// 保存
/// </summary>
public RelayCommand SaveCommand { get; set; } /// <summary>
/// 新建配方
/// </summary>
public RelayCommand CreateCommand { get; set; } public RelayCommand<SlideP16ItemCombine> DeleteCommand { get; set; } public RelayCommand SizeChangedCommand { get; private set; } #endregion Command private static readonly string SOURCE_DEFAULT =
@"pack://application:,,,/TestWPF;component/Resources/Images/upload.png"; /// <summary>
/// 初始化界面主题
/// </summary>
private void InitEnv()
{
string hexColor = "#F56C6C";
BrushConverter converter = new BrushConverter();
brushDanger = (Brush)converter.ConvertFromString(hexColor);
shadowEffect = new DropShadowEffect
{
Color = Colors.White, // 阴影颜色
Direction = 0, // 阴影方向
BlurRadius = 3, // 模糊半径
Opacity = 0.6, // 阴影不透明度
ShadowDepth = 0
};
} /// <summary>
/// 初始化绑定命令
/// </summary>
private void InitCommand()
{
CmdLeftMouseDown = new RelayCommand<MouseEventArgs>(LeftMouseDown);
CmdLeftMouseMove = new RelayCommand<MouseEventArgs>(LeftMouseMove);
CmdLeftMouseUp = new RelayCommand<MouseEventArgs>(LeftMouseUp);
MouseRightButtonUp = new RelayCommand<MouseEventArgs>(RightMouseUp);
ClearCommand = new RelayCommand(() =>
{
ClearRectangle();
});
UndoCommand = new RelayCommand(() =>
{
if (canvas.Children.Count > 0)
{
if (canvas.Children[canvas.Children.Count - 1] is Rectangle rect)
{
RemoveRectangle(rect);
}
// canvas.Children.RemoveAt(canvas.Children.Count - 1);
}
});
SaveCommand = new RelayCommand(() =>
{
SaveResult();
});
CreateCommand = new RelayCommand(() =>
{
CreateNewItem(SOURCE_DEFAULT);
}); DeleteCommand = new RelayCommand<SlideP16ItemCombine>(
(SlideP16ItemCombine param) =>
{
SlideControlParams.Remove(param);
return;
}
);
OnListViewItemDoubleClick = new RelayCommand<SlideP16ItemCombine>(
(args) =>
{
args.EditStatus = Visibility.Visible;
}
);
RefreshCommand = new RelayCommand(RefreshData);
SizeChangedCommand = new RelayCommand(Control_SizeChanged);
} #region Command对应方法 /// <summary>
/// 左键按下事件
/// </summary>
/// <param name="e"></param>
private void LeftMouseDown(MouseEventArgs e)
{
if (SelectedSlide == null)
return;
if (SelectedSlide.ImagePath == SOURCE_DEFAULT)
{
LoadLocalImage();
return;
}
isDrawing = true; if (e.OriginalSource is Canvas)
{
startPoint = e.GetPosition((IInputElement)e.Source);
}
else
{
startPoint = (e.Source as Rectangle).TranslatePoint(
e.GetPosition((IInputElement)e.Source),
canvas
);
}
// startPoint = e.GetPosition((IInputElement)e.Source);
tempRect = CreateTempRect();
Trace.WriteLine("down");
canvas.Children.Add(tempRect);
} private void RightMouseUp(MouseEventArgs e)
{
if (SelectedSlide == null)
return;
if (e.OriginalSource is Rectangle)
{
var rect = e.OriginalSource as Rectangle;
RemoveRectangle(rect);
}
} private void LeftMouseUp(MouseEventArgs e)
{
if (SelectedSlide == null)
return;
isDrawing = false;
AddRectangle();
tempRect = null;
} private void LeftMouseMove(MouseEventArgs e)
{
if (SelectedSlide == null)
return;
if (!isDrawing)
return;
if (e.OriginalSource is Canvas)
{
endPoint = e.GetPosition((IInputElement)e.Source);
}
else
{
endPoint = (e.Source as Rectangle).TranslatePoint(
e.GetPosition((IInputElement)e.Source),
canvas
);
}
UpdateRectangle();
} #endregion Command对应方法 #region other func private Rectangle CreateTempRect()
{
Rectangle rect = new Rectangle
{
Width = 0,
Height = 0,
Stroke = brushDanger, // Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.Transparent,
IsHitTestVisible = true
}; rect.Effect = shadowEffect;
Canvas.SetLeft(rect, startPoint.X);
Canvas.SetTop(rect, startPoint.Y); return rect;
} /// <summary>
/// 刷新rect轨迹,并不会添加到数组
/// </summary>
private void UpdateRectangle()
{
AddRectangle(true);
return;
} private void AddRectangle(bool isUpdate = false)
{
try
{
double width = (endPoint.X - startPoint.X);
double height = endPoint.Y - startPoint.Y;
double left = startPoint.X;
double top = startPoint.Y;
if (width < 0)
{
left = endPoint.X;
width = -width;
}
if (height < 0)
{
top = endPoint.Y;
height = -height;
}
tempRect.Width = width;
tempRect.Height = height; Canvas.SetLeft(tempRect, left);
Canvas.SetTop(tempRect, top); if (!isUpdate) //如果完成,则添加到列表
{
var tempPoint = new SlideP16ItemPoint
{
Height = height,
Width = width,
X = left,
Y = top,
IsImagePoint = false,
};
SelectedSlide.ItemPoints.Add(tempPoint);
AddControlPoint(SelectedSlide.ItemPoints[SelectedSlide.ItemPoints.Count - 1]);
}
}
catch (Exception ex)
{
return;
}
} /// <summary>
/// 删除界面指定rectangle对象,并从绑定的队列移除
/// </summary>
/// <param name="rect"></param>
private void RemoveRectangle(Rectangle rect)
{
var imagePoint = ConvertToImageCoordinates(
new SlideP16ItemPoint
{
X = Canvas.GetLeft(rect),
Y = Canvas.GetTop(rect),
Width = rect.Width,
Height = rect.Height
}
); var toDeletePoint = this
.SelectedSlide.ItemPoints.Where(x =>
x.IsImagePoint
&& Math.Abs(x.Width - imagePoint.Width) <= 0.1
&& Math.Abs(x.Height - imagePoint.Height) <= 0.1
&& Math.Abs(x.X - imagePoint.X) <= 0.1
&& Math.Abs(x.Y - imagePoint.Y) <= 0.1
)
.FirstOrDefault();
if (toDeletePoint != null)
SelectedSlide.ItemPoints.Remove(toDeletePoint);
canvas.Children.Remove(rect);
} /// <summary>
/// 删除所有绘图
/// </summary>
private void ClearRectangle()
{
canvas.Children.Clear();
this.SelectedSlide.ItemPoints.Clear();
} /// <summary>
/// 控件坐标转换为图像坐标
/// </summary>
/// <param name="pointControl"></param>
/// <returns></returns>
private SlideP16ItemPoint ConvertToImageCoordinates(SlideP16ItemPoint pointControl)
{

double scaleX = 1;
double scaleY = 1;
if (imgSlide.Source is BitmapSource bmp)
{
scaleX = imgSlide.ActualWidth
/ (imgSlide.Source == null ? imgSlide.ActualWidth : bmp.PixelWidth);
scaleY =
imgSlide.ActualHeight
/ (imgSlide.Source == null ? imgSlide.ActualHeight : bmp.PixelHeight);
}

double imageRectX = pointControl.X / scaleX;
double imageRectY = pointControl.Y / scaleY;
double imageRectWidth = pointControl.Width / scaleX;
double imageRectHeight = pointControl.Height / scaleY;
return new SlideP16ItemPoint
{
Height = imageRectHeight,
Width = imageRectWidth,
X = imageRectX,
Y = imageRectY,
};
} //private double scaleX;
//private double scaleY;
/// <summary>
/// 图像坐标转控件坐标
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
private SlideP16ItemPoint ConvertToControlCoordinates(SlideP16ItemPoint point)
{
var scaleX = imgSlide.ActualWidth / imgSlide.Source.Width;
var scaleY = imgSlide.ActualHeight / imgSlide.Source.Height;
// 根据缩放比例计算控件上的位置和尺寸
double controlX = point.X * scaleX;
double controlY = point.Y * scaleY;
double controlWidth = point.Width * scaleX;
double controlHeight = point.Height * scaleY;
return new SlideP16ItemPoint
{
Height = controlHeight,
Width = controlWidth,
X = controlX,
Y = controlY,
};
} private void LoadConfig()
{
string jsonPath = System.Environment.CurrentDirectory;
jsonPath = System.IO.Path.Combine(jsonPath, "ScanPara", "slideParms.json");
var json = File.ReadAllText(jsonPath);
SlideP16Parms rt = JsonConvert.DeserializeObject<SlideP16Parms>(json);
IEnumerable<SlideP16ItemCombine> items = rt.Items.Select(x => new SlideP16ItemCombine
{
EditStatus = Visibility.Collapsed,
ID = x.ID,
ImagePath = x.ImagePath,
ItemPoints = x.ItemPoints,
Name = x.Name
});
SlideControlParams = new ObservableCollection<SlideP16ItemCombine>(items);
} /// <summary>
/// 加载point坐标,并绘制到ui
/// </summary>
/// <param name="isControl"></param>
private void LoadPoints()
{
imgSlide.Dispatcher.Invoke(
new Action(() =>
{
if (this.SelectedSlide == null)
return;
imgMarginPos = imgSlide.TranslatePoint(new Point(0, 0), canvas);
canvas.Children.Clear();
foreach (SlideP16ItemPoint p in SelectedSlide.ItemPoints)
{
SlideP16ItemPoint t_point = null; if (p.IsImagePoint)
t_point = ConvertToControlCoordinates(p);
else
t_point = p; Rectangle tempRect = new Rectangle
{
Width = t_point.Width,
Height = t_point.Height,
Stroke = brushDanger, // Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.Transparent,
IsHitTestVisible = true
}; tempRect.Effect = shadowEffect;
Canvas.SetLeft(tempRect, t_point.X);
Canvas.SetTop(tempRect, t_point.Y);
// Trace.WriteLine($"tempRect x:{t_point.X} y:{t_point.Y},width:{tempRect.Width},height:{tempRect.Height}");
canvas.Children.Add(tempRect);
}
// Trace.WriteLine("cavnas.children.count:" + canvas.Children.Count);
}),
DispatcherPriority.Loaded
);
} private void AddControlPoint(SlideP16ItemPoint t_point)
{
Rectangle tempRect = new Rectangle
{
Width = t_point.Width,
Height = t_point.Height,
Stroke = brushDanger, // Brushes.Black,
StrokeThickness = 2,
Fill = Brushes.Transparent,
IsHitTestVisible = true
}; tempRect.Effect = shadowEffect;
Canvas.SetLeft(tempRect, t_point.X);
Canvas.SetTop(tempRect, t_point.Y);
// canvas.Children.Add(tempRect);
Trace.WriteLine("cavnas.children.count:" + canvas.Children.Count);
} private void CreateNewItem(string imgPath)
{
int id = 0;
if (this.SlideControlParams?.Count > 0)
id = this.SlideControlParams.Max(x => x.ID) + 1;
var item = new SlideP16ItemCombine()
{
ID = id,
ImagePath = imgPath,
Name = id.ToString(),
ItemPoints = new List<SlideP16ItemPoint>() { },
EditStatus = Visibility.Visible
}; this.SlideControlParams.Add(item);
//this.SlideImageParams.Add(item);
this.SelectedSlide = item;
} private void LoadLocalImage()
{
// 创建 OpenFileDialog 对象
OpenFileDialog openFileDialog = new OpenFileDialog();
// 设置文件类型过滤器,只允许选择图片文件
openFileDialog.Filter =
"Image files (*.jpg;*.jpeg;*.bmp;*.png)|*.jpg;*.jpeg;*.bmp;*.png|All files (*.*)|*.*";
// 设置默认文件类型筛选器
openFileDialog.FilterIndex = 1;
// 是否允许多选
openFileDialog.Multiselect = false;
// 显示对话框并获取用户的操作结果
bool? result = openFileDialog.ShowDialog();
// 如果用户点击了确认按钮
if (result == true)
{
try
{
// 获取选择的文件名
string fileName = openFileDialog.FileName;
FileInfo fn = new FileInfo(fileName);
string targeImgPath = System.IO.Path.Combine(
imgDir,
"P16_" + SelectedSlide.ID.ToString() + fn.Extension
);
File.Copy(fileName, targeImgPath, true);
// 将 BitmapImage 对象设置为界面上的 Image 控件的 Source
SelectedSlide.ImagePath = targeImgPath; // bitmapImage;
}
catch (Exception ex)
{
// 处理异常情况
HandyControl.Controls.MessageBox.Show(
$"Error loading image: {ex.Message}",
"Error",
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
}
} /// <summary>
/// 保存结果到json
/// </summary>
private void SaveResult()
{
SlideP16Parms prams = new SlideP16Parms();
prams.Items = this.SlideControlParams.Select(x => (SlideP16Item)x).ToList();
foreach (var p in prams.Items)
{
for (int i = 0; i < p.ItemPoints.Count; i++)
{
if (!p.ItemPoints[i].IsImagePoint)
p.ItemPoints[i] = ConvertToImageCoordinates(p.ItemPoints[i]); // ConvertToImageCoordinates();
}
}
var str = JsonConvert.SerializeObject(prams, Formatting.Indented); // JsonConvert.SerializeObject(prams, Formatting.Indented);
File.WriteAllText(jsonPath, str);
} /// <summary>
/// 刷新页面数据,重新读取
/// </summary>
private void RefreshData()
{
LoadConfig();
} private void Control_SizeChanged()
{
if (imgSlide.Source == null || canvas.Children.Count == 0)
return;
var scaleX = imgSlide.ActualWidth / imgSlide.Source.Width;
var scaleY = imgSlide.ActualHeight / imgSlide.Source.Height;
imgMarginPos = imgSlide.TranslatePoint(new Point(0, 0), canvas); //将Image控件的(0,0)坐标点转换为相对于canvas的坐标点。返回的Point对象就表示了Image控件在canvas中的偏移量。
var offetX = imgMarginPos.X;
var offetY = imgMarginPos.Y;
for (int i = 0; i < canvas.Children.Count; i++)
{
var element = canvas.Children[i] as Rectangle; var tempRect = SelectedSlide.ItemPoints[i];
//if (!tempRect.IsImagePoint)
//{
// tempRect = ConvertToImageCoordinates(tempRect); //不可行,因为此时转换的时候,已经是size-changed之后了,无法正确转换
//}
var cav = VisualTreeHelper.GetParent(imgSlide) as Canvas; double left = tempRect.X;
double top = tempRect.Y;
left = left * scaleX;
top = top * scaleY;
left += offetX;
top += offetY;
var width = tempRect.Width; // element.ActualWidth;
var height = tempRect.Height; // element.ActualHeight;
width *= scaleX;
height *= scaleY;
Trace.WriteLine("rq.scaleX:" + scaleX);
Trace.WriteLine("rq.scaleY:" + scaleY);
Trace.WriteLine("rq.element width:" + width);
Trace.WriteLine("rq.element height:" + height);
Canvas.SetLeft(element, left);
Canvas.SetTop(element, top);
element.Width = width;
element.Height = height;
Trace.WriteLine("rq.left:" + left);
Trace.WriteLine("rq.top:" + top);
Trace.WriteLine("rq.width:" + width);
Trace.WriteLine("rq.height:" + height);
}
} #endregion other func
} #region json映射类 P16玻片参数 public class SlideP16ItemCombine : SlideP16Item
{
private Visibility _EditStatus; public Visibility EditStatus
{
get => _EditStatus;
set => Set(ref _EditStatus, value);
}
} public class SlideP16Parms
{
public int ApplyID { get; set; }
public List<SlideP16Item> Items { get; set; }
} /// <summary>
/// P16玻片配置信息
/// </summary>
public class SlideP16Item : ViewModelBase
{
public int ID { get; set; }
public List<SlideP16ItemPoint> ItemPoints { get; set; } private string _Name; public string Name
{
get => _Name;
set => Set(ref _Name, value);
} private string _ImagePath; /// <summary>
/// 图片路径
/// </summary>
public string ImagePath
{
get => _ImagePath;
set => Set(ref _ImagePath, value);
}
} /// <summary>
/// p16玻片子配置
/// </summary>
public class SlideP16ItemPoint
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; } /// <summary>
/// 是否为图像坐标,json读写忽略
/// </summary>
[JsonIgnore]
public bool IsImagePoint { get; set; } = true;
} #endregion json映射类
}

2024年5月16日补充

其中,控件坐标转图像坐标

  /// <summary>
/// 控件坐标转换为图像坐标
/// </summary>
/// <param name="pointControl"></param>
/// <returns></returns>
private SlideP16ItemPoint ConvertToImageCoordinates(SlideP16ItemPoint pointControl)
{ double scaleX = 1;
double scaleY = 1;
if (imgSlide.Source is BitmapSource bmp)
{
scaleX = imgSlide.ActualWidth
/ (imgSlide.Source == null ? imgSlide.ActualWidth : bmp.PixelWidth);
scaleY =
imgSlide.ActualHeight
/ (imgSlide.Source == null ? imgSlide.ActualHeight : bmp.PixelHeight);
} double imageRectX = pointControl.X / scaleX;
double imageRectY = pointControl.Y / scaleY;
double imageRectWidth = pointControl.Width / scaleX;
double imageRectHeight = pointControl.Height / scaleY;
return new SlideP16ItemPoint
{
Height = imageRectHeight,
Width = imageRectWidth,
X = imageRectX,
Y = imageRectY,
};
}

不能直接取source的高度和宽度,

图像在容器中的原始高度,因为默认情况下 Image 控件会按照图像的原始宽高比来显示图像。因此,我们需要从 BitmapSource 对象中获取实际图像的宽度和高度。

应该改为通过 PixelWidthPixelHeight 属性获取图像的实际宽度和高度

同时,canvas为了更好的计算,高度和宽度应该改为绑定image的控件宽度和高度,真要不要计算边界坐标差值了.

                <Image Source="pack://application:,,,/RQTool;component/Content/Images/upload.png"  Grid.Column="1" Width="{Binding ActualWidth,ElementName=pnlImg}"
Height="{Binding ActualHeight,ElementName=pnlImg}" x:Name="imgCapture"/>
<Canvas Grid.Row="1" Name="canvas" Background="#19DAB1B1" Grid.Column="1" Height="{Binding ElementName=imgCapture,Path=ActualHeight}" Width="{Binding ElementName=imgCapture,Path=ActualWidth}" >

WPF Canvas在Image 图像上绘图,自适应缩放.的更多相关文章

  1. 用python, PIL在图像上添加文字(可以控制,调节为水印等)

    最近想在图像上,添加想要的文字,首先想到的是matplotlib,但是这个更加倾向于画图(柱状图,折线图之类) opencv这个库肯定也行,但是为了和我现有程序连接在一起,我选择了PIL 其中字体的设 ...

  2. WPF学习(11)2D绘图

    本篇我们来学习WPF的绘图,在2D绘图中主要有这么几个重要的类:Drawing.Visual和Shape,顺便讲下Brush和BitmapEffect. 1 2D绘图 1.1Drawing类 Draw ...

  3. Javascript高级编程学习笔记(90)—— Canvas(7) 绘制图像

    绘制图像 2D绘图上下文内置了对图像的支持 如果希望将一幅图绘制到画布上,可以使用 drawImage() 的方法 该方法有三种不同的参数数组合以对应不同的应用场景 将<img>绘制到画布 ...

  4. 【WPF】如何使用wpf实现屏幕最前端的绘图?

    原文:[WPF]如何使用wpf实现屏幕最前端的绘图? 引言 在知乎上面看到如何使用wpf实现屏幕最前端的绘图? 这么一个问题,觉得全屏弹幕很有趣,所以把它实现了. 实现 界面设置很简单,Window界 ...

  5. HTML5<canvas>标签:使用canvas元素在网页上绘制线条和圆(1)

    什么是 Canvas? HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像. 画布是一个矩形区域,您可以控制其每一像素. canvas 拥有多种绘制路径.矩形.圆形.字符以 ...

  6. 有关Canvas的一点小事--鼠标绘图

    1.  如何根据鼠标位置获取canvas上对应位置的x,y. 2.  canvas的图糊了,设置宽和高的方式不对. 3.鼠标绘图代码 之前听说过canvas这个元素,但是实际上并没有深入了解过.不过日 ...

  7. OpenCV之响应鼠标(四):在图像上绘制出矩形并标出起点的坐标

    涉及到两方面的内容:1. 用鼠标画出矩形.2.在图像上绘制出点的坐标 用鼠标绘制矩形,涉及到鼠标的操作,opencv中有鼠标事件的介绍.需要用到两个函数:回调函数CvMouseCallback和注册回 ...

  8. iOS:CALayer核心动画层上绘图

    在CALayer上绘图: •要在CALayer上绘图,有两种方法: 1.创建一个CALayer的子类,然后覆盖drawInContext:方法,可以使用Quartz2D API在其中进行绘图 2.设置 ...

  9. [图像]用Matlab在图像上画矩形框

    原创文章,欢迎转载.转载请注明:转载自 祥的博客 原文链接:http://blog.csdn.net/humanking7/article/details/46819527 在毕业设计的时候写论文画图 ...

  10. WPF中的文字修饰——上划线,中划线,基线与下划线

    原文:WPF中的文字修饰——上划线,中划线,基线与下划线 我们知道,文字的修饰包括:空心字.立体字.划线字.阴影字.加粗.倾斜等.这里只说划线字的修饰方式,按划线的位置,我们可将之分为:上划线.中划线 ...

随机推荐

  1. Apache Flink 在京东的实践与优化

    ​简介: Flink 助力京东实时计算平台朝着批流一体的方向演进. 本文整理自京东高级技术专家付海涛在 Flink Forward Asia 2020 分享的议题<Apache Flink 在京 ...

  2. [MySQL] 原生全文检索 fulltext 的简单应用

    在目标字段上添加全文检索:alter table 表名 add fulltext(字段) with parser ngram 查询语句:select * from xxx where match(字段 ...

  3. CSS:鼠标移动到图片上的动画

    CSS:鼠标移动到图片上的动画 .pic img { width: 100%; left: 0; top: 0; right: 0; bottom: 0; margin: auto; transiti ...

  4. Oracle和达梦:根据外键名字查询表名

    根据外键名字查询表名 select * from user_cons_columns cl where cl.constraint_name = '外键名';

  5. 关于QQ群炸了的说明

    ABAP 7.5学习群不幸被腾讯封了,想要聊天的群友可以加以下两个群, ABAP 7.5历史研究小组 728466742 ABAP 7.5 备份群 582240105

  6. 从大数据平台CDP的架构看大数据的发展趋势

    CDP(Cloudera Data Platform)是Cloudera 和 HortonWorks 合并后推出的新一代大数据平台 ,并正在逐步停止对原有的大数据平台 CDH 和 HDP 的维护.笔记 ...

  7. 通过劫持线程arena实现任意地址分配 n1ctf2018_null

    通过劫持线程arena,当堆开了一个线程之后,如果没有做好保护随之的危险也悄然而至 BUU上的n1ctf2018_null很好的说明了这个问题 题目链接:BUUCTF在线评测 (buuoj.cn) 看 ...

  8. CSS样式(第二篇)

    ​ CSS样式(第二篇) 关于定位position: li.widget {position: relative;}相对定位,设定一个参照物. @media (max-width: 980px) .t ...

  9. pageoffice6 版本实现word 文件添加水印

    在很多场景下,Word文档正式发文之前,或者说形成最终文档之前,常常需要往Word文件中添加水印,并且会根据文件类型或内容的不同,需要添加的水印也不一样. 添加水印是Word软件里的一个简单功能,直接 ...

  10. PageOffice 6 给SaveFilePage指向的保存地址传参

    PageOffice给保存方法传递参数的方式有两种: 通过设置保存地址的url中的?传递参数.例如: poCtrl.setSaveFilePage("/save?p1=1") 通过 ...