You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF图片绘制矩形生成CroppedBitmap出现偏移问题(C#)

解决WPF图片裁剪偏移问题

问题根源分析

你的偏移问题核心来自两个错误:

  1. 比例计算逻辑错误:混淆了「原图像素尺寸」和「控件显示尺寸」的关系,直接用原图尺寸除以Source.Width(本身就是原图像素宽)得到的比例无实际意义,且用Convert.ToInt32强制转整数会丢失精度,导致累积偏移。
  2. 未考虑图片在控件内的偏移:当Image控件使用Uniform等拉伸模式时,图片会在控件内居中显示,上下/左右存在空白区域,鼠标坐标未扣除这些偏移就直接转换,会导致坐标对应错误。

修正步骤

1. 确保Canvas与Image控件显示区域对齐

先让Canvas1的尺寸完全匹配ImageViewer1的实际显示尺寸,避免鼠标坐标错位:

<!-- 在XAML中绑定Canvas尺寸到Image控件的实际尺寸 -->
<Canvas x:Name="Canvas1" 
        Width="{Binding ActualWidth, ElementName=ImageViewer1}" 
        Height="{Binding ActualHeight, ElementName=ImageViewer1}"
        Panel.ZIndex="1"/>

2. 计算图片的缩放比例与偏移量

在MouseUp事件中,先计算图片在Image控件内的实际缩放因子和空白偏移:

BitmapSource source = (BitmapSource)ImageViewer1.Source;
double viewerWidth = ImageViewer1.ActualWidth;
double viewerHeight = ImageViewer1.ActualHeight;
double imagePixelWidth = source.PixelWidth;
double imagePixelHeight = source.PixelHeight;

double actualScale = 0;
double offsetX = 0;
double offsetY = 0;

// 根据Image控件的Stretch模式计算缩放和偏移
switch (ImageViewer1.Stretch)
{
    case Stretch.Uniform:
        actualScale = Math.Min(viewerWidth / imagePixelWidth, viewerHeight / imagePixelHeight);
        offsetX = (viewerWidth - imagePixelWidth * actualScale) / 2;
        offsetY = (viewerHeight - imagePixelHeight * actualScale) / 2;
        break;
    case Stretch.UniformToFill:
        actualScale = Math.Max(viewerWidth / imagePixelWidth, viewerHeight / imagePixelHeight);
        offsetX = (viewerWidth - imagePixelWidth * actualScale) / 2;
        offsetY = (viewerHeight - imagePixelHeight * actualScale) / 2;
        break;
    case Stretch.Fill:
        actualScale = 1; // Fill模式下X/Y方向分别缩放,后续单独处理
        break;
    case Stretch.None:
        actualScale = 1;
        offsetX = (viewerWidth - imagePixelWidth) / 2;
        offsetY = (viewerHeight - imagePixelHeight) / 2;
        break;
}

3. 将鼠标坐标转换为图片像素坐标

扣除控件内的空白偏移,再除以缩放比例,得到对应原图的像素坐标:

// 转换鼠标按下坐标
double pixelDownX = (mouseDownPoint.X - offsetX) / (ImageViewer1.Stretch == Stretch.Fill ? (viewerWidth / imagePixelWidth) : actualScale);
double pixelDownY = (mouseDownPoint.Y - offsetY) / (ImageViewer1.Stretch == Stretch.Fill ? (viewerHeight / imagePixelHeight) : actualScale);

// 转换鼠标抬起坐标
double pixelUpX = (mouseUpPoint.X - offsetX) / (ImageViewer1.Stretch == Stretch.Fill ? (viewerWidth / imagePixelWidth) : actualScale);
double pixelUpY = (mouseUpPoint.Y - offsetY) / (ImageViewer1.Stretch == Stretch.Fill ? (viewerHeight / imagePixelHeight) : actualScale);

4. 计算裁剪矩形并做边界检查

用Math.Round取整(比直接截断更准确),同时确保裁剪区域不超出图片范围:

int rectX = (int)Math.Round(Math.Min(pixelDownX, pixelUpX));
int rectY = (int)Math.Round(Math.Min(pixelDownY, pixelUpY));
int rectWidth = (int)Math.Round(Math.Abs(pixelUpX - pixelDownX));
int rectHeight = (int)Math.Round(Math.Abs(pixelUpY - pixelDownY));

// 边界检查,避免CroppedBitmap报错
rectX = Math.Max(0, rectX);
rectY = Math.Max(0, rectY);
rectWidth = Math.Min(rectWidth, imagePixelWidth - rectX);
rectHeight = Math.Min(rectHeight, imagePixelHeight - rectY);

5. 生成裁剪图片

最后使用修正后的参数创建CroppedBitmap:

CroppedBitmap croppedBitmap = new CroppedBitmap(source, new Int32Rect(rectX, rectY, rectWidth, rectHeight));
ImageViewer2.Source = croppedBitmap;

关键注意点

  • 不要用Convert.ToInt32处理比例或坐标,会丢失小数精度导致累积偏移,改用Math.Round取整。
  • 必须根据Image控件的Stretch模式调整计算逻辑,不同拉伸模式下图片的缩放和偏移规则不同。
  • 始终做边界检查,防止裁剪区域超出图片像素范围引发异常。

内容的提问来源于stack exchange,提问作者Laila A.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:33:14