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

WPF图像裁剪应用坐标偏差问题求助

WPF图像裁剪:鼠标绘制矩形与实际裁剪区域不匹配问题

问题背景

开发WPF应用时,实现加载图像并通过鼠标绘制矩形裁剪功能,遇到鼠标绘制区域和实际裁剪结果不匹配的问题。尝试结合图像DPI与屏幕DPI计算坐标后,裁剪结果显示为原图的放大局部;后续修改逻辑后,图像直接无法显示。

XAML代码

<Grid Name="GridImage">
    <Image Name="LoadedImage" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="HighQuality">
    </Image>
    <Canvas  x:Name="ImageCanvas" ClipToBounds="True"
            IsHitTestVisible="True" MouseLeftButtonDown="ImageCanvas_MouseLeftButtonDown" MouseMove="ImageCanvas_MouseMove"
            MouseUp="ImageCanvas_MouseUp"
            Background="Transparent">
        <Rectangle x:Name="SelectionRectangle" Stroke="LightBlue" Fill="#220000FF" Visibility="Collapsed" />
    </Canvas>
</Grid>

Canvas事件处理代码

private void ImageCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed)
    {
        isDrawing = true;
        startPoint = e.GetPosition(ImageCanvas);
        Canvas.SetLeft(SelectionRectangle, startPoint.X);
        Canvas.SetTop(SelectionRectangle, startPoint.Y);
        SelectionRectangle.Width = 0;
        SelectionRectangle.Height = 0;
        SelectionRectangle.Visibility = Visibility.Visible;
    }
}

private void ImageCanvas_MouseMove(object sender, MouseEventArgs e)
{
    if (isDrawing)
    {
        Point currentPoint = e.GetPosition(ImageCanvas);

        double width = Math.Abs(currentPoint.X - startPoint.X);
        double height = Math.Abs(currentPoint.Y - startPoint.Y);

        width = Math.Min(width, LoadedImage.ActualWidth - Canvas.GetLeft(SelectionRectangle));
        height = Math.Min(height, LoadedImage.ActualHeight - Canvas.GetTop(SelectionRectangle));

        SelectionRectangle.Width = width;
        SelectionRectangle.Height = height;
        Canvas.SetLeft(SelectionRectangle, Math.Min(startPoint.X, currentPoint.X));
        Canvas.SetTop(SelectionRectangle, Math.Min(startPoint.Y, currentPoint.Y));
    }
}

private void ImageCanvas_MouseUp(object sender, MouseButtonEventArgs e)
{
    Debug.WriteLine("ImageCanvas_MouseUp event triggered");
    isDrawing = false;
 
    CropImage();
}

第一次修改的裁剪方法(裁剪结果为原图放大局部)

尝试结合DPI计算坐标,但未得到正确结果:

private void Crop()
{
    var imagePosition = LoadedImage.TransformToAncestor(GridImage).Transform(new Point(0, 0));

    double imageDpiX = 96.0; 
    double imageDpiY = 96.0;
    Rect rect1 = new Rect(Math.Max(Canvas.GetLeft(SelectionRectangle) - imagePosition.X, 0), Math.Max(Canvas.GetTop(SelectionRectangle) - imagePosition.Y, 0), SelectionRectangle.Width, SelectionRectangle.Height);
    if (LoadedImage.Source is BitmapSource bitmapSource)
    {
        imageDpiX = bitmapSource.DpiX;
        imageDpiY = bitmapSource.DpiY;
    }
    
     //Set the screen DPI
    double screenDpiX = 96.0;
    double screenDpiY = 96.0;
    
    // Calculate the DPI ratios
    double ratioX = screenDpiX / imageDpiX;
    double ratioY = screenDpiY / imageDpiY;
    
    Int32Rect rcFrom = new Int32Rect
    {
        X = (int)(rect1.X * ratioX),
        Y = (int)(rect1.Y * ratioY),
        Width = (int)(rect1.Width * ratioX),
        Height = (int)(rect1.Height * ratioY)
    };
    
    try
    {
        BitmapSource bs = new CroppedBitmap((BitmapSource)LoadedImage.Source, rcFrom);
        LoadedImage.Source = bs;
        SetImageStretch(LoadedImage);
        SetElementVisibility(Visibility.Hidden, Visibility.Visible, SelectionRectangle);
    }
}

编辑后的修改(图像无法显示)

修改ImageCanvas_MouseUp事件:

Point mousePosition = e.GetPosition(LoadedImage);

double mouseX = mousePosition.X;
double mouseY = mousePosition.Y;
CropImage(mouseX, mouseY);

修改CropImage方法的裁剪区域计算:

var width = x - Canvas.GetLeft(SelectionRectangle);
var height = y - Canvas.GetTop(SelectionRectangle);

Int32Rect rcFrom = new Int32Rect
{
    X = (int)x,
    Y = (int)y,
    Width = (int)width,
    Height = (int)height
};

问题

  1. 第一次修改后,裁剪结果为原图放大局部,未匹配鼠标绘制区域;
  2. 编辑后的修改导致图像完全无法显示,求解决方法。

解决方案

核心问题分析

  • 第一次修改的DPI比例逻辑错误:CroppedBitmap使用图像原始像素坐标,需要将屏幕显示坐标转换为图像原始像素坐标,正确比例应为图像DPI / 屏幕DPI,而非反过来。
  • 编辑后的修改逻辑漏洞:直接用鼠标坐标作为裁剪起始点忽略了图像缩放比例,且width = x - Canvas.GetLeft(...)会出现负数(鼠标在起始点左侧时),导致Int32Rect无效,图像无法显示。

修正后的裁剪方法

private void CropImage()
{
    if (LoadedImage.Source is not BitmapSource originalBitmap)
        return;

    // 获取选择矩形在Image控件上的相对坐标
    Point rectTopLeft = new Point(Canvas.GetLeft(SelectionRectangle), Canvas.GetTop(SelectionRectangle));
    Rect selectionRect = new Rect(rectTopLeft, new Size(SelectionRectangle.Width, SelectionRectangle.Height));

    // 计算图像缩放比例:原始像素尺寸 / 显示尺寸
    double scaleX = originalBitmap.PixelWidth / LoadedImage.ActualWidth;
    double scaleY = originalBitmap.PixelHeight / LoadedImage.ActualHeight;

    // 结合DPI修正坐标(WPF默认基准DPI为96)
    double dpiScaleX = originalBitmap.DpiX / 96.0;
    double dpiScaleY = originalBitmap.DpiY / 96.0;

    // 转换为原始图像的像素坐标
    int cropX = (int)Math.Round(selectionRect.X * scaleX * dpiScaleX);
    int cropY = (int)Math.Round(selectionRect.Y * scaleY * dpiScaleY);
    int cropWidth = (int)Math.Round(selectionRect.Width * scaleX * dpiScaleX);
    int cropHeight = (int)Math.Round(selectionRect.Height * scaleY * dpiScaleY);

    // 确保裁剪区域在原始图像范围内
    cropX = Math.Max(0, cropX);
    cropY = Math.Max(0, cropY);
    cropWidth = Math.Min(cropWidth, originalBitmap.PixelWidth - cropX);
    cropHeight = Math.Min(cropHeight, originalBitmap.PixelHeight - cropY);

    // 避免无效裁剪区域
    if (cropWidth <= 0 || cropHeight <= 0)
        return;

    Int32Rect cropArea = new Int32Rect(cropX, cropY, cropWidth, cropHeight);
    CroppedBitmap croppedBitmap = new CroppedBitmap(originalBitmap, cropArea);

    // 更新Image源并设置合适的拉伸模式
    LoadedImage.Source = croppedBitmap;
    LoadedImage.Stretch = Stretch.Uniform;
    SelectionRectangle.Visibility = Visibility.Collapsed;
}

关键说明

  • 缩放比例计算:scaleX和scaleY将屏幕显示尺寸映射回图像原始像素尺寸,确保鼠标绘制的区域对应正确的原始像素范围。
  • DPI修正:若图像DPI与屏幕基准96DPI不一致,通过图像DPI/96的比值修正坐标,保证高DPI图像的裁剪准确性。
  • 边界检查:避免裁剪区域超出原始图像范围,防止CroppedBitmap抛出异常或显示空白。

内容的提问来源于stack exchange,提问作者user21124346

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 23:36:05